DP基本思想
将求解的问题分解成若干子问题,先求解子问题,再从这些子问题得到原问题的解。
DP的两个基本要素(重要性质):
1. 最优子结构性质。即问题的最优解包含了其子问题的最优解。
2. 重叠子问题性质。有的子问题可能会被计算多次。因此采用备忘录方法避免对相同子问题的重复求解。
DP编程关键
状态转移方程、边界条件
最长公共子序列(LCS)
Description:
给两个长度为n的全排列,求其最长公共子序列长度。
Input:
第一行是一个正整数N,表示全排列长度。
第二行有n个整数,保证是一个n的全排列。
第三行有n个整数,保证是一个n的全排列。
Output:
输出第一行有一个整数,表示两数组最长公共子序列长度。
Sample Input:
5
1 3 2 4 5
5 2 3 1 4
Sample Output:
2
#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
const int MAX = 1e3+7;
int dp[MAX][MAX];
int str1[MAX],str2[MAX];
int main(void){
int n;
while(cin>>n){
for(int i=1;i<=n;i++) cin>>str1[i];
for(int i=1;i<=n;i++) cin>>str2[i];
memset(dp,0,sizeof(dp));
for(int i=0;i<=n;i++){
for(int j=0;j<=n;j++){
if(i==0||j==0) dp[i][j]=0;
else if(str1[i]==str2[j]) dp[i][j]=dp[i-1][j-1]+1;
else dp[i][j]=max(dp[i-1][j],dp[i][j-1]);
}
}
cout<<dp[n][n]<<endl;
}
return 0;
}
Coincidence
题目描述
Find a longest common subsequence of two strings.
输入描述
First and second line of each input case contain two strings of lowercase character a…z. There are no spaces before, inside or after the strings. Lengths of strings do not exceed 100.
输出描述
For each case, output k – the length of a longest common subsequence in one line.
输入
abcd
cxbydz
输出
2
和上面的题其实完全一样。
#include<iostream>
#include<cstring>
using namespace std;
const int MAX =105;
int dp[MAX][MAX];
int max(int a,int b){
return a>b?a:b;
}
int main(void)
{
char a[105],b[105];
while(scanf("%s%s",&a,&b)!=EOF){
int a_len = strlen(a);
int b_len = strlen(b);
memset(dp,0,sizeof(dp));
for(int i=1;i<=a_len;i++){
for(int j=1;j<=b_len;j++){
if(a[i-1]==b[j-1]) dp[i][j]=dp[i-1][j-1]+1;
else dp[i][j]=max(dp[i-1][j],dp[i][j-1]);
}
}
cout<<dp[a_len][b_len]<<endl;
}
return 0;
}
如果要求找出这个最长公共子序列是什么呢?考虑记录每一次的转移方向。
思路:用flag[][]记住当前状态是从什么方向转移而来的,然后从flag[n][n]开始进行回溯。如果flag[i][j]==0,则说明此时str1[i]==str2[j],状态dp[i][j]是从dp[i-1][j-1]转移而来的,此时打印出这个字符。
#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
const int MAX = 1e3+7;
int dp[MAX][MAX];
int flag[MAX][MAX];
int str1[MAX],str2[MAX];
void printSeq(int i,int j)
{
if(i==0||j==0) return;
if(flag[i][j]==0){ //该位置从左上方转移而来,说明出现了str1[i]==str2[j]
cout<<str1[i]<<" ";
printSeq(i-1,j-1);
}
if(flag[i][j]==1){ //从上方转移而来
printSeq(i-1,j);
}
if(flag[i][j]==-1){//从左方转移而来
printSeq(i,j-1);
}
}
int main(void){
int n;
while(cin>>n){
for(int i=1;i<=n;i++) cin>>str1[i];
for(int i=1;i<=n;i++) cin>>str2[i];
memset(dp,0,sizeof(dp));
memset(flag,0,sizeof(flag));
for(int i=1;i<=n;i++){
for(int j=1;j<=n;j++){
if(str1[i]==str2[j]){
dp[i][j]=dp[i-1][j-1]+1;
flag[i][j]=0;//表示从左上方转移而来
}
else
{
if(dp[i-1][j]>dp[i][j-1]){
dp[i][j]=dp[i-1][j];
flag[i][j]=1;//表示从上方转移而来
}
else{
dp[i][j]=dp[i][j-1];
flag[i][j]=-1;//表示从左方转移而来
}
}
//dp[i][j]=max(dp[i-1][j],dp[i][j-1]);
}
}
cout<<dp[n][n]<<endl;
printSeq(n,n);//倒序打印
}
return 0;
}
动态规划问题总结
【模板】动态规划之背包问题(01背包、多重背包、完全背包) (邮票、Piggy-bank题解)
【模板】动态规划之最长上升子序列LIS问题、概率DP问题 (烟花题解)