一 数塔问题
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;
}