区间dp总结篇

时间:2022-01-13 09:10:01

前言:这两天没有写什么题目,把前两周做的有些意思的背包题和最长递增、公共子序列写了个总结。反过去写总结,总能让自己有一番收获......就区间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(最优三角划分)

区间dp总结篇

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));
}