动态规划基本问题

来源:互联网 发布:linux 宕机日志 编辑:程序博客网 时间:2024/06/11 19:59

一  数塔问题

dp[i][j]表示从第i行第j个数字到出发到达最底层的所有路径中能得到的最大和。


边界:dp[n][j]=f[n][j];


状态转移方程l dp[i][j]=max(dp[i+1][j],dp[i+1][j+1])+f[i][j];


动态规划的代码如下;


#include<cstdio>#include<iostream>#include<cstring>#include<algorithm>using namespace std;const int maxn=110;int f[maxn][maxn];///存储数塔,第i行第j个数的值int dp[maxn][maxn];///从dii行第j个数字出发到达最底层的所有路径中的最大和int main(){    int n;    while(scanf("%d",&n)!=EOF)    {        for(int i=1;i<=n;i++)            for(int j=1;j<=i;j++)            scanf("%d",&f[i][j]);        for(int j=1;j<=n;j++)///边界            dp[n][j]=f[n][j];                    for(int i=n-1;i>=1;i--)            for(int j=1;j<=i;j++)        {            dp[i][j]=max(dp[i+1][j],dp[i+1][j+1])+f[i][j];        }        printf("%d\n",dp[1][1]);    }    return 0;}


二 最大连续子序列的和


问题描述如下:

    给定一个数字序列A1,A2,.......An, 求 i,j (1<=i<=j), 使得Ai +.......+Aj 最大,输出这个最大和。


dp[i]表示以 A[i] 为末尾的连续序列的最大和 (这里是说A[i]必须作为连续序列的末尾)。


边界:dp[0]=A[0];


下面想办法求解dp数组:


dp[i]必须是以 A[i] 为结尾的连续序列,那么只有两种情况:


(1) 这个最大和的连续序列只有一个元素,即以 A[i] 开始,以 A[i] 结尾。


(2)这个最大和的连续序列有多个元素,即从前面某处 A[p]开始 (p<i) ,一直到 A[i] 结尾


对第一种情况:最大和就是 A[i] 本身


对第二种情况,最大和是 dp[i-1] + A[i] , 即 A[p] +.........+A[i-1] +A[i] =dp[i-1] + A[i];


于是得到状态转移方程:      dp[i] = max{ A[i] , dp[i-1]+ A[i] }.


代码如下:


///dp[i]为以A[i]末尾的连续序列的最大和///状态转移方程:dp[i]=max{A[i], dp[i-1]+A[i]}#include<cstdio>#include<iostream>#include<algorithm>using namespace std;const int maxn=1e4+10;int A[maxn],dp[maxn];int main(){    int n;    scanf("%d",&n);    for(int i=0;i<n;i++)        scanf("%d",&A[i]);    dp[0]=A[0];///边界        for(int i=1;i<n;i++)///状态转移方程        dp[i]=max(A[i],dp[i-1]+A[i]);    int k=0;    for(int i=1;i<n;i++)        if(dp[i]>dp[k])        k=i;///k记录dp[]数组最大值的下标    printf("%d\n",dp[k]);    return 0; }


三 最长不下降子序列 (LIS)


问题描述如下:

       在一个数字序列中,找到一个最长的子序列 (可以不连续) ,使得这个子序列是不下降的(非递减的)。


 dp[i]表示以 A[i] 为结尾的最长不下降子序列的长度 (和最大连续子序列一样,以A[i] 为结尾是强制要求)。那么对于


A[i] 来说就会有两种可能:


(1) 如果存在A[i] 之前的元素 A[j] ,是的 A[j] <= A[i] 且 dp[j] +1 >dp[i] ( 即把 A[i] 跟在以A[j] 结尾的LIS后面时能比当


前以A[i] 结尾的LIS 长度更长)。


(2) 如果A[i]之前的元素都比 A[i] 大,那么A[i] 就只好自己形成一个LIS 但是长度为1.


边界 dp[i] =1;


状态转移方程:   dp[i] = max{1,dp[ j ]+1 }  (j=1,2,3,.....i-1&& A[j]<=A[i])

 

代码如下:


///dp[i]表示以A[i]为末尾的最长不下降子序列的长度///状态转移方程dp[i]=max{1,dp[j]+1} j<i&&A[j]<A[i]///边界 dp[1]=1;#include<cstdio>#include<iostream>#include<algorithm>using namespace std;const int N=100;int A[N],dp[N];int main(){    int n;    scanf("%d",&n);    for(int i=1;i<=n;i++)        scanf("%d",&A[i]);    int ans=-1;///记录最大的dp[i]    for(int i=1;i<=n;i++)    {        dp[i]=1;        for(int j=1;j<i;j++)           {               if(A[i]>=A[j]&&(dp[j]+1>dp[i]))              dp[i]=dp[j]+1;           }        ans=max(ans,dp[i]);    }    printf("%d\n",ans);    return 0;}

  


四 最长公共子序列 (LCS)


问题描述:

       给定两个字符串 (或数字序列) A 和 B,求一个字符串,使得这个字符串是A 和 B 的最长公共部分 (子序列可以不连续)。

     样例:字符串 "sadstory"与 "adminsorry"的最长公共子序列为 "adsory",长度为6.


dp[i][ j]表示字符串 A 与字符串 B 的j号位之前的的 LCS 长度,那么可以根据 A[i] 和 B[j]的情况,分为两种决策:


(1)若A[i]==B[j], 则字符串 A 与字符串 B 的 LCS 增加了一位, 即有 dp[i][j]=dp[i-1][j-1]+1,例如 样例中的dp[4][6]表


示"sads"与"admins"的LCS长度,比较A[4]与A[6],发现两者都是"s",因此dp[4][6]就等于dp[3][5]+1,即为3


(2)若 A[i]!=B[j] ,则字符串A 的i号位和字符串B的j号位之前的 LCS 无法延长,因此dp[i][j] 将会继承 dp[i-1][j] 与


dp[i][j-1] 中的较大值,即有dp[i][j]=max{dp[i-1][j], dp[i][j-1]}.


边界:dp[i][0] = dp[0][j] =0;  (0<=i<=n, 0<=j<=m)     由边界条件递推至整个dp数组


状态转移方程:

                       dp[i][j] =                 dp[i-1][j-1]+1  ,     A[i]==B[j]

                                                            max{dp[i-1][j],dp[i][j-1]} ,   A[i]!=B[j]  


代码;

#include<cstdio.>#include<iostream>#include<cstring>#include<algorithm>using namespace std;const int N=100;char A[N],B[N];int dp[N][N];int main(){    int n;    gets(A+1);    gets(B+1);    int lenA=strlen(A+1);    int lenB=strlen(B+1);    ///边界    for(int i=0;i<=lenA;i++)        dp[i][0]=0;    for(int j=0;j<=lenB;j++)        dp[0][j]=0;    ///状态转移方程    for(int i=1;i<=lenA;i++)    {        for(int j=1;j<=lenB;j++)            if(A[i]==B[j])        {            dp[i][j]=dp[i-1][j-1]+1;        }        else        {            dp[i][j]=max(dp[i-1][j],dp[i][j-1]);        }    }    printf("%d\n",dp[lenA][lenB]);    return 0;}


五  最长回文子串


问题描述:

       给出一个字符串 S,求 S 的最长回文子串


样例: 字符串 "PATZJUJZTACCBCC " 的最长回文子串为 "ATZJUJZTA" ,长度为9


dp[i][j] 表示S[i] 至 S[j] 所表示的子串是否是回文子串,是则为 1,不是为 0,这样根据 S[i] 是否等于 S[j] ,可以把转移


情况分为两类


(1) 若 S[i]==S[j],那么只要S[i+1] 至 S[j-1]是回文子串,S[i] 至 S[j] 就是回文子串;如果S[i+1] 至 S[j-1] 不是回文子串


,则S[i]至S[j] 也不是回文子串。


(2)若S[i]!=S[j], 那么S[i]至S[j] 一定不是回文子串。


由此可以写出状态转移方程:

                                           dp[i][j]=   dp[i+1][j-1]  S[i] == S[j]

                                                            0                  S[i] != S[j]


边界: dp[i][i]=1;  dp[i][i+1]=(S[i]==S[i=1])?1:0


枚举方式:根据递推写法从边界出发的原理,注意到边界表示的是长度为1 和 2 的字符串,且每次转移时都对子串的


长度减一,因此不妨考虑按子串的长度和子串的初始位置进行枚举,即第一遍将长度为 3 的子串的dp值全部求出,第


二遍通过第一遍的结果将长度为4 的子串的dp值。。。。。。这样就能避免状态无法转移的问题。


代码:


#include<cstdio>#include<iostream>#include<cstring>#include<algorithm>using namespace std;const int maxn=1e3+10;char s[maxn];int dp[maxn][maxn];int main(){    gets(s);    int len=strlen(s);    int ans=1;    memset(dp,0,sizeof(dp));    ///边界    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;        }    }    ///状态转移方程    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;///更新最长回文子串的长度            }        }    }    printf("%d\n",ans);    return 0;}