1. 数字三角形/数塔问题(DP入门题)
有形如下图所示的数塔,从顶部出发,在每一结点可以选择向左走或是向右走,一起走到底层,要求找出一条路径,使路径上的值最大。
样例输入:
5
13
11 8
12 7 26
6 14 15 8
12 7 13 24 11
样例输出:
86(13->8->26->15->24)
状态转移方程:dp[i][j]=max(dp[i+1][j],dp[i+1][j+1])+a[i][j]; 从最低层往上搜索
#include <cstring>
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cmath>
#include <string.h>
//#include <map>
#define ll long long
using namespace std;
#define pai acos(-1,0)
int map[355][355];
int dp[355][355];
int main()
{
int n;
cin>>n;
for(int i=1;i<=n;i++)
{
for(int j=1;j<=i;j++)
{
cin>>map[i][j];
}
}
for(int i=1;i<=n;i++)
dp[n][i]=map[n][i];
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])+map[i][j];
}
cout<<dp[1][1]<<endl;
return 0;
}
2. 序列DP
(1)最长上升子序列LIS
输入n及一个长度为n的数列,求出此序列的最长上升子序列长度。上升子序列指的是对于任意的i<j都满足ai<aj的子序列。(1<=n<=1000,0<=ai<=1000000)
样例输入:
5
4 2 3 1 5
样例输出:
3(最长上升子序列为2, 3, 5)
#include <iostream>
#include <cstdio>
#define maxn 1005
using namespace std;
int n,a[maxn];
int dp[maxn]; //dp[i]记录以a[i]为末尾的最长上升子序列的长度
int main()
{
int i,j;
int ret;
while(scanf("%d",&n)!=EOF)
{
for(i=1;i<=n;i++)
{
scanf("%d",&a[i]);
dp[i]=1; //初始化
}
ret=1;
for(i=1;i<=n;i++)
{
for(j=1;j<i;j++) //遍历所有在a[i]之前的元素
{
if(a[j]<a[i]) //若存在aj<ai,则在以aj为结尾的上升子列末尾追加ai后得到的子序列 和 只包含ai的子序列中取长度较大者
dp[i]=max(dp[i],dp[j]+1);
}
ret=max(ret,dp[i]); //注意随时更新ret
}
printf("%d\n",ret);
}
return 0;
}
(2)最长公共子序列LCS
给定两个字符串s1和s2(长度均不超过1000),求出这两个字符串的最长公共子序列的长度。
【分析】定义dp[i][j]:串s1的前i个字符 和 串s2的前j个字符的最长公共子序列长度,则s1…si+1和t1…tj+1对应的公共子列可能是:
①si+1=tj+1时:在s1…si 和 t1…tj的公共子列末尾追加si+1(即LCS长度+1)
②否则可能为s1…si和t1…tj+1的公共子列长度l1 或s1…si+1和t1…tj的公共子列长度l2,二者取较大者。
故状态转移方程为:
dp[i+1][j+1]=dp[i][j]+1, ①
max(dp[i][j+1], dp[i+1][j]), ②
最后dp[len1][len2]即为所求,其中len1、len2分别为串s1和s2的长度。
#include <iostream>
#include <cstdio>
#include <cstring>
using namespace std;
const int maxlen=1010;
char s1[maxlen],s2[maxlen];
int dp[maxlen][maxlen]; //dp[i][j]记录串s1的前i个字符和串s2的前j个字符的LCS长度
int main()
{
int i,j;
int len1,len2;
while(scanf("%s",s1)!=EOF)
{
scanf("%s",s2);
len1=strlen(s1);
len2=strlen(s2);
dp[0][0]=0; //初始化:两串均为空时,len(LCS)=0
for(i=1;i<=len1;i++)//s2串为空时,不论s1中有多少字符,len(LCS)=0
dp[i][0]=0;
for(i=1;i<=len2;i++)//s1串为空时,不论s1中有多少字符,len(LCS)=0
dp[0][i]=0;
for(i=0;i<len1;i++)
{
for(j=0;j<len2;j++)
{
if(s1[i]==s2[j]) //s1与s2对应位置字符相等
dp[i+1][j+1]=dp[i][j]+1;
else //其它情况:两者取较大者
dp[i+1][j+1]=max(dp[i][j+1],dp[i+1][j]);
}
}
printf("%d\n",dp[len1][len2]);
}
return 0;
}
(3)最大公共子串LCS
给定两个字符串s1和s2(长度均不超过1000),求出这两个字符串的最大公共子串的长度。
【分析】情境类似求最长公共子序列长度问题,不过需要注意的是:所求子串中的字符需要在串s1和串s2中连续出现。
例:s1=”abcad”
s2=”abd”
它们的最长公共子序列长度为3(”abd”),而最大公共子串长度为2(”ab”)。
因此,定义dp[i][j]:串s1的前i个字符 和 串s2的前j个字符的最大公共子串长度,则s1…si+1和t1…tj+1对应的公共子串可能是:
①si+1=tj+1时:在s1…si 和 t1…tj的公共子串末尾追加si+1(即LCS长度+1)
②否则dp[i][j]=0。
分析可知状态转移方程:
dp[i+1][j+1]=dp[i][j]+1, ①
0, ②
#include <iostream>
#include <cstdio>
#include <cstring>
using namespace std;
const int maxlen=1010;
char s1[maxlen],s2[maxlen];
int dp[maxlen][maxlen]; //dp[i][j]为串s1的前i个字符和串s2的前j个字符的最大公共子串长度
int main()
{
int i,j;
int len1,len2,ret; //ret记录结果
while(scanf("%s",s1)!=EOF)
{
scanf("%s",s2);
memset(dp,0,sizeof(dp)); //初始化:开始LCS长度均为0
len1=strlen(s1);
len2=strlen(s2);
ret=0;
for(i=0;i<len1;i++)
{
for(j=0;j<len2;j++)
{
if(s1[i]==s2[j])
dp[i+1][j+1]=dp[i][j]+1;
else
dp[i+1][j+1]=0;
ret=max(ret,dp[i+1][j+1]); //随时更新最大值
}
}
printf("%d\n",ret);
}
return 0;
}