区间dp总结篇
前言:这两天没有写什么题目,把前两周做的有些意思的背包题和最长递增、公共子序列写了个总结。反过去写总结,总能让自己有一番收获......就区间dp来说,一开始我完全不明白它是怎么应用的,甚至于看解题报告都看不明白,而到了现在,遇到区间dp之类的题目,我不至于没有任何方向,慢慢的推导,有些题目没有自己想象的那么难,还是可以推导出转移方程的,有些题目,在自己推导过后,与解题报告相对照,也总能有一番全新的收获。我是觉得,解题报告需要看,但是怎么看,如何看,却是值得思量.......
1、Light oj 1422 Halloween Costumes
题意:给你n天需要穿的衣服的样式,每次可以套着穿衣服,脱掉的衣服就不能再用了(可以再穿),问至少要带多少条衣服才能参加所有宴会
http://lightoj.com/volume_showproblem.php?problem=1422
思路:首先dp[i][j]代表从区间i到区间j需要的最少穿衣服数量,我采取的是从下向上更新的。那么,面临第i件衣服,首先我们考虑穿上它,那么它所在的区间dp[i][j]=dp[i+1][j]+1;
接着考虑是否可以不用穿上它?在什么条件下,可以不用穿这件衣服呢?只有当区间i+1~~j里面已经穿过这件衣服的时候,就可以考虑不用再穿这件衣服。那么假设i+1<=k<=j
其中第k件衣服与第i件一样,那么第k件衣服穿上,第i件衣服不穿的情况的最少穿衣数==dp[i+1][k-1]+dp[k][j];第i件衣服不穿,那么就从第i+1件衣服开始计算,第k件衣服存在,那么在这个断点,我们该怎么判断是dp[i+1][k-1]+dp[k][j]呢?可以直接从数据推导出这个关系,也可以这样,在第k区间的时候,a[k]可能不是它自己本身穿的,而是在第k区间到第j区间,存在一个k<tmp<=j,有a[tmp]==a[k]......所以会是dp[i+1][k-1]+dp[k][j]........
#include<iostream> #include<stdio.h> #include<string.h> using namespace std; int min(int x,int y) { if(x>y) return y; else return x; } int dp[105][105],a[105]; int main() { int text,h=0; scanf("%d",&text); while(text--) { int n; scanf("%d",&n); for(int i=1;i<=n;i++) scanf("%d",&a[i]); memset(dp,0,sizeof(dp)); for(int i=1;i<=n;i++) dp[i][i]=1; for(int i=n-1;i>=1;i--) { for(int j=i+1;j<=n;j++) { dp[i][j]=dp[i+1][j]+1; for(int k=i+1;k<=j;k++) if(a[i]==a[k]) { dp[i][j]=min(dp[i][j],dp[i+1][k-1]+dp[k][j]); } } } printf("Case %d: %d\n",++h,dp[1][n]); } return 0; }
2、 poj2955(括号匹配问题)
题意:给你一串()[]括号,要你求出这串括号的最大匹配个数,如'('与')'匹配,为2个,'['与']'匹配,为2个,其他不能匹配.......
思路:dp[i][j]代表从区间i到区间j所匹配的括号的最大个数,首先,假设不匹配,那么dp[i][j]=dp[i+1][j];然后查找i+1~~j有木有与第i个括号匹配的
有的话,dp[i][j]=max(dp[i][j],dp[i+1][k-1]+dp[k][j]+2).....
#include<iostream> #include<stdio.h> #include<string.h> using namespace std; int dp[105][105]; char a[105]; int max(int x,int y) { if(x>y) return x; else return y; } int main() { while(scanf("%s",a+1)>0&&a[1]!='e') { a[0]=2; int len=strlen(a); len--; //printf("%d\n\n",len); //for(int i=1;i<=len;i++) //printf("%d %c\n",i,a[i]); memset(dp,0,sizeof(dp)); for(int i=len-1;i>=1;i--) { for(int j=i+1;j<=len;j++) { dp[i][j]=dp[i+1][j]; for(int k=i+1;k<=j;k++) { if((a[i]=='('&&a[k]==')')||(a[i]=='['&&a[k]==']')) { dp[i][j]=max(dp[i][j],dp[i+1][k-1]+dp[k][j]+2); //printf("%d %d %c %c\n",i,k,a[i],a[k]); } } } } printf("%d\n",dp[1][len]); } return 0; }
3、poj3280(推荐)
题意:给你m个字符,其中有n种字符,每种字符都有两个值,分别是增加一个这样的字符的代价,删除一个这样的字符的代价,让你求将原先给出的那串字符变成回文串的最小代价。
思路:dp[i][j]代表区间i到区间j成为回文串的最小代价,那么对于dp[i][j]有三种情况:
1、dp[i+1][j]表示区间i到区间j已经是回文串了的最小代价,那么对于s[i]这个字母,我们有两种操作,删除与添加,对应有两种代价,dp[i+1][j]+add[s[i]],dp[i+1][j]+del[s[i]],取这两种代价的最小值;
2、dp[i][j-1]表示区间i到区间j-1已经是回文串了的最小代价,那么对于s[j]这个字母,同样有两种操作,dp[i][j-1]+add[s[j]],dp[i][j-1]+del[s[j]],取最小值
3、若是s[i]==s[j],dp[i+1][j-1]表示区间i+1到区间j-1已经是回文串的最小代价,那么对于这种情况,我们考虑dp[i][j]与dp[i+1][j-1]的大小........
然后dp[i][j]取上面这些情况的最小值.........
#include<iostream> #include<stdio.h> #include<string.h> using namespace std; int dp[2005][2005],add[27],del[27]; char s[2005]; int min(int x,int y) { if(x>y) return y; else return x; } int main() { int n,m; while(scanf("%d%d",&n,&m)>0) { scanf("%s",s+1); s[0]=2; for(int i=1;i<=n;i++) { char ch[10]; int tmp1,tmp2; scanf("%s%d%d",ch,&tmp1,&tmp2); add[ch[0]-'a'+1]=tmp1; del[ch[0]-'a'+1]=tmp2; } memset(dp,0,sizeof(dp)); for(int i=m-1;i>=1;i--) { for(int j=i+1;j<=m;j++) { dp[i][j]=min(dp[i+1][j]+add[s[i]-'a'+1],dp[i+1][j]+del[s[i]-'a'+1]); int tmp=min(dp[i][j-1]+add[s[j]-'a'+1],dp[i][j-1]+del[s[j]-'a'+1]); dp[i][j]=min(dp[i][j],tmp); if(s[i]==s[j]) dp[i][j]=min(dp[i][j],dp[i+1][j-1]); } } printf("%d\n",dp[1][m]); } return 0; }
4、poj1141(区间dp记录路径问题)
题意:给出一串括号,要你补上最少的括号使这一串括号都匹配........
思路:dp[i][j]表示从区间i到区间j使其所以括号匹配需要补上的最少括号数,那么当出现一个括号时,首先考虑它不与后面匹配的情况,那么需要加一个相对应的括号,让之匹配dp[i][j]=dp[i+1][j]+1;
然后再考虑,若是后面有括号可以让它匹配的情况,那么假设i<k<=j,当s[i]=='('&&s[k]==')'的时候,考虑动态转移,dp[i][j]=dp[i+1][k-1]+dp[k][j]-1
为什么这个动态方程减1呢,因为我将与之匹配的那个括号重新计算了一次,当s[k]==')'的时候,在计算dp[k][k]的时候,我的状态转移已经把这个括号自动匹配了一次,所以要减去这次匹配的........
然后就是记录路径了,开一个二维数组a[i][j],当a[i][j]==-1的时候,表示dp[i][j]这个状态是从dp[i+1][j]推导过来的,当a[i][j]>0的时候,表示dp[i][j]是从dp[i+1][a[i][j]-1]以及dp[k][j]这两个状态推导过来的,那么注意到当a[i][j]!=-1的时候,就正好表示s[i]与s[a[i][j]]匹配,说明在第i个括号这个地方只需要输出它自己本身,其他的,若是a[i][j]==-1的,都需要输出它自身以及与它自身匹配的括号.........
#include<iostream> #include<stdio.h> #include<string.h> using namespace std; int dp[300][300],a[300][300],b[300]; char s[300]; void print(int i,int j) { if(i>=j) return; if(a[i][j]==-1) print(i+1,j); if(a[i][j]>0) { b[i]=1; b[a[i][j]]=1; print(i+1,a[i][j]-1); print(a[i][j],j); } } int main() { while(gets(s+1)>0) //这里注意,有直接输入\n的情况......... { s[0]=2; memset(dp,0,sizeof(dp)); memset(a,-1,sizeof(a)); memset(b,0,sizeof(b)); int len=strlen(s); len--; for(int i=1;i<=len;i++) dp[i][i]=1; for(int i=len-1;i>=1;i--) { for(int j=i+1;j<=len;j++) { dp[i][j]=dp[i+1][j]+1; a[i][j]=-1; for(int k=i+1;k<=j;k++) if((s[i]=='('&&s[k]==')')||(s[i]=='['&&s[k]==']')) { if(dp[i][j]>dp[i+1][k-1]+dp[k][j]-1) { dp[i][j]=dp[i+1][k-1]+dp[k][j]-1; a[i][j]=k; } } } } print(1,len); for(int i=1;i<=len;i++) { if(b[i]==1) printf("%c",s[i]); else { if(s[i]=='('||s[i]==')') printf("()"); else printf("[]"); } } printf("\n"); } return 0; }
5、poj1651(推荐)
题意:给你一组数字,第一个和最后一个数字不可以取出去,其它任意取出去,当你要取出一个数字时,它有一个代价,这个代价就是与它相邻的两个数的乘积,求除了首位两位数字,把其他数字都取出来,它们的代价之和的最小值........
思路:这题目,只有自己做过才能体会......我是说不出来......
Sample Input 6 10 1 50 50 20 5 Sample Output 3650
#include<iostream> #include<stdio.h> #include<string.h> using namespace std; int dp[105][105],a[105]; int min(int x,int y) { if(x>y) return y; else return x; } int main() { int n; while(scanf("%d",&n)>0) { for(int i=1;i<=n;i++) scanf("%d",&a[i]); memset(dp,0,sizeof(dp)); for(int i=n-2;i>=1;i--) { dp[i][i+2]=a[i]*a[i+1]*a[i+2]; for(int j=i+3;j<=n;j++) { dp[i][j]=a[i]*a[i+1]*a[j]+dp[i+1][j]; dp[i][j]=min(dp[i][j],a[i]*a[j-1]*a[j]+dp[i][j-1]); for(int k=i+2;k<j-1;k++) dp[i][j]=min(a[i]*a[k]*a[j]+dp[i][k]+dp[k][j],dp[i][j]); } } printf("%d\n",dp[1][n]); } return 0; }
6、poj3661(推荐)
题意:给你一个n,m,n表示有n分钟,每i分钟对应的是第i分钟能跑的距离,m代表最大疲劳度,每跑一分钟疲劳度+1,当疲劳度==m,必须休息,在任意时刻都可以选择休息,如果选择休息,那么必须休息到疲劳度为0,当然,当疲劳度为0的时候也是可以继续选择休息的,求在n分钟后疲劳度为0所跑的最大距离
思路:dp[i][j]表示在第i分钟疲劳度为j的时候所跑的最大距离,dp[i][j]=dp[i-1][j-1]+d[i];这个转移,说的是,第i分钟选择跑步,当然,第i分钟可以选择不跑步,那么就是选择休息,题目说了,选择休息的话必须要休息到疲劳度为0才可以跑,那还有一点,当疲劳度为0了,还是选择继续休息呢?dp[i][0]=dp[i-1][0];
选择休息,那么疲劳度到0了,这一点的最大距离怎么做呢?dp[i][0]=max(dp[i][0],dp[i-k][k]) (0<k<=m&&i-k>0)
这样所有的状态都考虑完了,可以写代码了.......
#include<iostream> #include<stdio.h> #include<string.h> using namespace std; int dp[10005][505],a[10005]; int main() { int n,m; while(scanf("%d%d",&n,&m)>0) { for(int i=1;i<=n;i++) scanf("%d",&a[i]); memset(dp,0,sizeof(dp)); for(int i=1;i<=n;i++) { for(int j=1;j<=m;j++) dp[i][j]=dp[i-1][j-1]+a[i]; dp[i][0]=dp[i-1][0]; for(int k=1;k<=m;k++) if(i-k>=0) dp[i][0]=max(dp[i][0],dp[i-k][k]); } printf("%d\n",dp[n][0]); } return 0; }
7、hdu2476(推荐)
给出两串字符,要你将第一串字符变成第二串字符,每次可以改变连续的一个或多个字符,求最少的修改次数
思路:还是区间dp问题,说起来也挺简单的,只是记录路径问题,有些难以想到..........
#include<iostream> #include<stdio.h> #include<string.h> using namespace std; int dp[105][105],a[105]; char s[105],t[105]; int main() { while(scanf("%s",s+1)>0) { scanf("%s",t+1); s[0]=t[0]=2; int len=strlen(s); len--; memset(dp,0,sizeof(dp)); memset(a,0,sizeof(a)); for(int i=1;i<=len;i++) dp[i][i]=1; for(int i=len-1;i>=1;i--) { for(int j=i+1;j<=len;j++) { //if(s[i]!=t[i]) dp[i][j]=dp[i+1][j]+1; for(int k=i+1;k<=j;k++) if(t[i]==t[k]) { dp[i][j]=min(dp[i][j],dp[i+1][k-1]+dp[k][j]); } } } for(int i=1;i<=len;i++) { a[i]=dp[1][i]; if(s[i]==t[i]) { if(i==1) a[i]=0; else a[i]=a[i-1]; } else for(int j=1;j<i;j++) a[i]=min(a[i],a[j]+dp[j+1][i]); } printf("%d\n",a[len]); //printf("%d\n",dp[1][len]); } return 0; }
8、zoj3537(最优三角划分)
9、编辑距离问题
题目描述:
要求两字符串有差异的字符个数。例如:
aaaaabaaaaa
aaaaacaabaa
这两个字符串,最大公共字串长度是5,但它们只有两个字符不同,函数输出值应为2。
如果是:
aaabbbcccddd
aaaeeeddd
函数的输出值应该是6。
比较形象地形容一下,把两个字符串排成上下两行,每个字符串都可以在任何位置插入空格以便上下对齐,每个列上至少有一个字符来自这两个字符串。当对齐程度最高的时候,没有对上的列的数即为函数输出值。
aaabbbcccddd
aaaeeeddd
最优对齐状态是:
aaabbbcccddd
aaaeee ddd
没有对上的列是6,函数输出值为6。
如果是:
abcde
acefg
最优对齐状态是:
abcde
a c efg
没有对上的列数是4,函数输出值为4。
问题抽象归类:(编辑距离问题)
设A和B是2个字符串。要用最少的字符操作将字符串A转换为字符串B。这里所说的字符操作包括:
(1)删除一个字符;
(2)插入一个字符;
(3)将一个字符改为另一个字符。
将字符串A变换为字符串B所用的最少字符操作数称为字符串A到B的编辑距离,记为d(A,B)。试设计一个有效算法,对任给的2个字符串A和B,计算出它们的编辑距离d(A,B)。
要求:
输入:第1行是字符串A,第2行是字符串B。
输出:字符串A和B的编辑距离d(A,B)
思路:动态规划
开一个二维数组d[i][j]来记录a0-ai与b0-bj之间的编辑距离,要递推时,需要考虑对其中一个字符串的删除操作、插入操作和替换操作分别花费的开销,从中找出一个最小的开销即为所求
具体算法:
首先给定第一行和第一列,然后,每个值d[i,j]这样计算:d[i][j] = min(d[i-1][j]+1,d[i][j-1]+1,d[i-1][j-1]+(s1[i] == s2[j]?0:1));
最后一行,最后一列的那个值就是最小编辑距离
#include <stdio.h> #include <string.h> char s1[1000],s2[1000]; int min(int a,int b,int c) { int t = a < b ? a : b; return t < c ? t : c; } void editDistance(int len1,int len2) { int** d=new int*[len1+1]; for(int k=0;k<=len1;k++) d[k]=new int[len2+1]; int i,j; for(i = 0;i <= len1;i++) d[i][0] = i; for(j = 0;j <= len2;j++) d[0][j] = j; for(i = 1;i <= len1;i++) for(j = 1;j <= len2;j++) { int cost = s1[i] == s2[j] ? 0 : 1; int deletion = d[i-1][j] + 1; int insertion = d[i][j-1] + 1; int substitution = d[i-1][j-1] + cost; d[i][j] = min(deletion,insertion,substitution); } printf("%d\n",d[len1][len2]); for(int k=0;i<=len1;k++) delete[] d[k]; delete[] d; } int main() { while(scanf("%s %s",s1,s2) != EOF) editDistance(strlen(s1),strlen(s2)); }