动态规划
最大连续子列和
在一组连续的数字序列中 A1 ~ An中求得一个从Ai ~ Aj的序列,使得该序列的和最大,求此最大和的问题成为最大连续子列和。
使用一个dp数组记录子问题的结果,其中dp[i]表示以A[i]作为结尾的连续子的最大和(A[i]必须是这个序列的结尾),则dp[i]的获得有以下两种方法:
一、该序列只有A[i]开始,A[i]结束,所以其dp[i]值为A[i];
二、该序列从A[p]其中( p < i ) 开始一直到A[i]结束,测试dp[i]的值等于dp[i-1]+A[i];
由此变得出动态规划的递推关系,即状态转移方程:
dp[i]=max(A[i],dp[i-1]+A[i]);
在更新dp[i]的时候更新最大值,或者到dp数组求解完成后遍历一遍dp数组得到最大值,便是最大连续子列和,其代码实现如下:
#define MAXN 1000 #define INF 0x3fffffff int A[MAXN],dp[MAXN]; //A数组存放原始数据序列 int main(){ int n; cin>>n; int maxsum=-INF for(int i=0;i<n;i++){ cin>>A[i]; if(i==0) dp[0]=A[0]; //初始化dp[0]为第一个元素 if(i>0) dp[i]=max(A[i],dp[i-1]+A[i]); if(dp[i]>maxsum) maxsum=dp[i]; } cout<<maxsum; return 0; }
显然很容易发现这个题目,可以省去dp数组,直接在每一步进行判定,每次更新当前的dp值时,只用到之前的一个的数据,因此更靠前的结果便不必保存,这样的算法也称为在线处理。
最长不下降子序列(LIS)
在一个数字序列中,找到一组最长的子序列(可以连续,也可以不连续),使得这个子序列是非递减的,输出这个最长子序列的长度。
使用dp数组记录子问题的结果,dp[i]记录以A[i]结尾的最长不下降子列的长度,那么对于dp[i]的获得,有以下方法:
一、如果在元素A[i]之前存在一个元素A[j]是的A[i]>=A[j]并且dp[j]+1>dp[i],那么说明A[i]加入到以A[j]结束的最长不下降子序列中能够形成更长的LIS,因此更dp[i]=dp[j]+1;
二、如果A[i]之前的元素都比A[i]大,则其自身形成一个LIS,该序列中只有A[i]自己,因此长度为1;
由此,也能得到求解dp[i]时候的递推关系,即状态转移方程:
dp[i]=max(1,dp[j]+1); //其中j是A[i]能够加入形成更长的LIS情况
其代码实现如下:
#define MAXN 1000 int A[MAXN],dp[MAXN]; int main(){ int n; cin>>n; for(int i=0;i<n;i++){ //输入数据 cin>>A[i]; } int ans=-1; for(int i=1;i<n;i++){ //从第二个元素开始更新dp数组 dp[i]=1; //初始化为1,即只有自己构成LIS的情况 for(int j=0;j<i;j++){ //从头寻找是否有更优的解 if(A[i]>=A[j]&&dp[j]+1>dp[i]) dp[i]=dp[j]+1; } ans=max(ans,dp[i]); //更新并记录最优解 } cout<<ans; return 0; }
最长公共子序列(LCS)
给定两个字符串或者数字序列,A和B,求A和B最长的公共子序列的长度(子序列可以不连续)。
使用一个二维的dp数组记录子问题的结果,dp[i][j]表示A的i号位置和B的j号位置之前(包含i和j位)的LCS长度,则求解dp[i][j]的时候有以下情况:
一、如果A[i]==B[j]则字符串的的LCS就增加一位,dp[i][j]=dp[i-1][j-1]+1;
二、如果A[i]!=B[j],这时无法使得LCS更长。因此dp[i][j]需要继承dp[i-1][j]和dp[i][j-1]中较大的值,也就是前一个最长的LCS的数值。
由此得到求解dp[i][j]时候的递推关系,即状态转移方程:
A[i]==B[j]时,dp[i][j]=dp[i-1][j-1]+1; A[i]!=B[j]时,dp[i][j]=max(dp[i][j-1],dp[i-1][j]);
其代码实现如下:
#define MAXN 1000 char A[MAXN],B[MAXN]; int dp[MAXN][MAXN]; int main(){ int n; cin>>n; gets(A+1); //为了更容易判断,从下标1开始存储字符串 gets(B+1); int lena=strlen(A+1),lenb=strlen(B+1); for(int i=0;i<=lena;i++){ //初始化边界值 dp[i][0]=0; } for(int i=0;i<=lenb;i++){ dp[0][i]=0; } for(int i=1;i<=lena;i++){ for(int j=1;j<lenb;j++){ if(A[i]==B[i]) dp[i][j]=dp[i-1][j-1]+1; else dp[i][j]=max(dp[i-1][j],dp[i][j-1]); } } cout<<dp[lena][lenb]; //输出最终结果 }
最长回文子串
最长回文子串问题要解决给定的一个字符串的最长回文子串的长度。
对于一个字符串S来说,使用dp[i][j]表示字符串i到j位置是不是一个回文串(用1表示是,0表示不是),则求解dp[i][j]时可分为一下两种情况:
一、如果S[i]==S[j],如果S[i+1]到S[j-1]是回文串则S[i][j]也是回文串,否则不是回文串。
二、如果S[i]!=S[j],那么S[i]到S[j]一定不是回文串。
由此,可以得到求解dp[i][j]时候的递推关系,即状态转移方程:
S[i]==S[j],dp[i][j]=dp[i+1][j-1]; S[i]!=S[j],dp[i][j]=0;
其代码实现过程如下:
#define MAN 1000 char s[MAXN]; bool dp[MAXN][MAXN]={0}; int main(){ gets(s); int len=strlen(s),ans=1; //初始化答案为1,至少有一位 //状态边界 for(int i=0;i<len;i++){ dp[i][i]=1; if(i<len-1){ if(s[i]==s[i+1]){ dp[i][i+1]=1; ans=2; //如果有两位相邻且相同的,则答案至少为2 } } } //求解dp for(int L=3;L<=len;L++){ //枚举字串的长度 for(int i=0;i+L-1<len;i++){ //枚举字串的起始端点 int j=i+L-1; //计算字串的右端点 if(s[i]==s[j&&dp[i+1][j-1]==1]){ //状态转移方程 dp[i][j]=1; ans=L; } } } cout<<ans; return 0; }
Comments | NOTHING