区间DP
早就想写一份关于DP的专题了,可是毕竟太弱,,,,刷得太久了
先来个刷题链接:
bin神带你飞之区间DP
不是对于每个题都分析,分析各个弱会的好了:
1、Light oj 1422 Halloween Costumes
题意:给你n天需要穿的衣服的样式,每次可以套着穿衣服,脱掉的衣服就不能再用了(可以再穿),问至少要带多少条衣服才能参加所有宴会
lightoj1422
int dp[maxn][maxn];
int num[maxn];
int main(){
//input;
int t,n,i,j,k,len;
scanf("%d",&t);
for(int Case=1;Case<=t;Case++){
scanf("%d",&n);
for(i=1;i<=n;i++) scanf("%d",&num[i]);
memset(dp,0,sizeof(dp));
for(i=1;i<=n;i++) dp[i][i]=1;
for(i=n-1;i>=1;i--)
for(len=2;len+i<=n+1;len++){
j=i+len-1;
if (num[i]!=num[i+1]) dp[i][j]=dp[i+1][j]+1;
else dp[i][j]=dp[i+1][j];
for(k=i+1;k<=j;k++)
if (num[k]==num[i])
dp[i][j]=min(dp[i][j],dp[i+1][k-1]+dp[k][j]);
}
printf("Case %d: %d\n",Case,dp[1][n]);
}
return 0;
}
/*
dp[i][j]表示第i到第j最少需要多少衣服
那么有两种扩展方案:
1.穿衣服。那么若由后往前更新:
即:已知dp[i+1][j]推dp[i][j]
2.脱衣服。与其他的状态进行组合
*/
2、 poj2955(括号匹配问题)
题意:给你一串()[]括号,要你求出这串括号的最大匹配个数,如'('与')'匹配,为2个,'['与']'匹配,为2个,其他不能匹配.......
POJ2955
思路:dp[i][j]为i到j区间中最大的括号匹配数量
个人偏好记忆化
初始条件有:l==r,l+1==r,分析方案递推有:区间的左和右匹配,区间的左和右不匹配
int dp[maxn][maxn];
char s[maxn];
int GetDp(int l,int r){
if (dp[l][r]!=-1) return dp[l][r];
if (l+1==r){
if ((s[l]=='('&&s[r]==')')||(s[l]=='['&&s[r]==']')) return dp[l][r]=2;
return dp[l][r]=0;
}
if (l==r) return dp[l][r]=0;
if ((s[l]=='('&&s[r]==')')||(s[l]=='['&&s[r]==']')) dp[l][r]=2+GetDp(l+1,r-1);
for(int i=l;i<r;i++)
dp[l][r]=max(GetDp(l,i)+GetDp(i+1,r),dp[l][r]);
return dp[l][r];
}
int main(){
//input;
int n,i,j;
while(scanf("%s",s+1)){
if (s[1]=='e') break;
n=strlen(s+1);
memset(dp,-1,sizeof(dp));
printf("%d\n",GetDp(1,n));
}
return 0;
}
3、poj3280(推荐)
题意:给你m个字符,其中有n种字符,每种字符都有两个值,分别是增加一个这样的字符的代价,删除一个这样的字符的代价,让你求将原先给出的那串字符变成回文串的最小代价。
POJ3280
这个题首先可以剪枝:添加和删除对应的其实没有区别:如abcb可以添加a(添加在右边)或者删除a(在左边删除),得到的结果其实对其他的状态没有影响,因此在输入时候,添加和删除的情况记录费用最小的就好
dp的思路:dp【i】【j】同样的,同上题的记忆化
但是注意这题的“区间”可递推的地方只有两个:左端点和右端点
这还可以引出另外一类题型:
状态设计如下:dp【i】【j】【0】为区间【i】【j】且停留在左边的时候的最值,dp【i】【j】【1】为区间【i】【j】且停留在右边的时候的最值。。与本题无关。。。
int n,m;
char s[maxm],op[5];
int cost[maxn];
int dp[maxm][maxm];
int GetDp(int l,int r){
if (dp[l][r]!=-1) return dp[l][r];
if (l==r) return dp[l][r]=0;
if (l+1==r){
if (s[l]==s[r]) return dp[l][r]=0;
return dp[l][r]=min(cost[s[l]-'a'],cost[s[r]-'a']);
}
if (s[l]==s[r]) dp[l][r]=GetDp(l+1,r-1);
else dp[l][r]=min(GetDp(l+1,r)+cost[s[l]-'a'],GetDp(l,r-1)+cost[s[r]-'a']);
return dp[l][r];
}
int main(){
//input;
int i,j,k,x,y;
while(scanf("%d%d",&n,&m)!=EOF){
scanf("%s",s+1);
for(i=1;i<=n;i++){
scanf("%s%d%d",op,&x,&y);
cost[op[0]-'a']=min(x,y);
}
memset(dp,-1,sizeof(dp));
printf("%d\n",GetDp(1,m));
}
return 0;
}
POJ又跪了。。。搞得都没有心情写了。。。碎觉碎觉
——————————————————————————————————————————————————
强行一条分界线明天继续
4、poj1141(区间dp记录路径问题)
题意:给出一串括号,要你补上最少的括号使这一串括号都匹配........
思路:dp[i][j]表示从区间i到区间j使其所以括号匹配需要补上的最少括号数POJ1141
这是我毛毛雨教我做人的一题系列,主要的难度是递归输出方案,其中dp【i】【j】取得最值时,那么需要记录a【i】【j】的分段点为k,于是,就需要递归输出方案::那么推荐弱的另外一篇博客:
Dp中记录方案递归输出
代码如下:
char c[120];
long f[120][120];
long g[120][120];
long n;
void prt(long l, long r)
{
if (r == l)
{
if ((c[l] == '(') || (c[l] == ')')) printf("()");
if ((c[l] == '[') || (c[l] == ']')) printf("[]");
return ;
}
if (g[l][r] == -1)
{
if ( (c[l] == '(') && (c[r] == ')') )
{
printf("(");
prt(l + 1, r - 1);
printf(")");
}
if ( (c[l] == '[') && (c[r] == ']') )
{
printf("[");
prt(l + 1, r - 1);
printf("]");
}
}
else{
prt(l, g[l][r]);
prt(g[l][r] + 1, r);
}
}
void dp()
{
n = strlen(c);
memset(f, 127, sizeof(f));
memset(g, -1, sizeof(g));
for (long i = 0; i < n; i++)
{
f[i][i] = 1;
}
for (long l = 2; l <= n; l++)
{
for (long i =0; i + l - 1 < n; i++)
{
long j = i + l - 1;
if (((c[i] == '[') && (c[j] == ']')) || ((c[i] == '(') && (c[j] == ')')))
{
if (i + 1 == j) f[i][j] =0;
if (f[i][j] > f[i + 1][j - 1])
{
f[i][j] = f[i + 1][j - 1];
}
}
for (long k = i; k < j; k++)
{
if (f[i][j] > f[i][k] + f[k + 1][j])
{
f[i][j] = f[i][k] + f[k + 1][j];
g[i][j] = k;
}
}
}
}
//cout << f[0][n - 1] << endl;
}
int main()
{
while (gets(c))
{
if (strlen(c) == 0)
{
printf("\n");
continue;
}
dp();
prt(0, n - 1);
printf("\n");
}
return 0;
}
5、poj1651(推荐)
题意:给你一组数字,第一个和最后一个数字不可以取出去,其它任意取出去,当你要取出一个数字时,它有一个代价,这个代价就是与它相邻的两个数的乘积,求除了首位两位数字,把其他数字都取出来,它们的代价之和的最小值........
POJ1651
简单的区间DP题:枚举中间的每个位置即可,思路常规
int num[maxn];
int dp[maxn][maxn];
int i,j,k,t,n,len;
int main(){
//input;
while(scanf("%d",&n)!=EOF){
for(i=1;i<=n;i++) scanf("%d",&num[i]);
memset(dp,0,sizeof(dp));
for(i=1;i<n-1;i++)
for(j=i+2;j<=n;j++)
dp[i][j]=INF;
for(i=n-2;i>=1;i--)
for(len=3;len+i<=n+1;len++){
j=len+i-1;
for(k=i+1;k+1<=j;k++)
dp[i][j]=min(dp[i][j],dp[i][k]+dp[k][j]+num[i]*num[k]*num[j]);
}
//for(i=1;i<=n;i++)
//for(j=1;j<=n;j++)
// printf("%d%c",dp[i][j],j==n?'\n':' ');
printf("%d\n",dp[1][n]);
}
return 0;
}
6、poj3661(推荐)
题意:给你一个n,m,n表示有n分钟,每i分钟对应的是第i分钟能跑的距离,m代表最大疲劳度,每跑一分钟疲劳度+1,当疲劳度==m,必须休息,在任意时刻都可以选择休息,如果选择休息,那么必须休息到疲劳度为0,当然,当疲劳度为0的时候也是可以继续选择休息的,求在n分钟后疲劳度为0所跑的最大距离
POJ3661
自己感觉这个题不算是区间dp,而是最简单的DP
dp【i】【j】为第i分钟j点疲劳度时候取得的最大距离,那么最终答案为dp【n】【0】
递推有:dp[i][0]=max(dp[i-1][1],dp[i-1][0]) 当之前的疲劳度为0时,可以选择走或者不走
另外,枚举前一时刻的疲劳度递推得当前的dp值
int n,m;
int num[maxn];
int dp[maxn][maxm];
int main(){
//input;
int i,j,k;
while(scanf("%d%d",&n,&m)!=EOF){
for(i=1;i<=n;i++) scanf("%d",&num[i]);
memset(dp,-1,sizeof(dp));
dp[0][0]=0;
for(i=1;i<=n;i++){
dp[i][0]=max(dp[i-1][1],dp[i-1][0]);
for(j=1;j<=m&&j<=i;j++){
if (dp[i-1][j-1]!=-1) dp[i][j]=max(dp[i][j],dp[i-1][j-1]+num[i]);
dp[i][0]=max(dp[i][0],dp[i-j][j]);
}
}
printf("%d\n",dp[n][0]);
}
return 0;
}
7、hdu2476(推荐)
给出两串字符,要你将第一串字符变成第二串字符,每次可以改变连续的一个或多个字符,求最少的修改次数
HDOJ2476
理论解释见此文 http://www.2cto.com/kf/201208/149559.html
编程代码见此文 http://www.cnblogs.com/kuangbin/archive/2013/04/30/3052043.html
——————————————————————————————————————————————————————————————
区间Dp方法总结
与普通dp一样,最难的还是设计状态及其转移
状态一般为dp【i】【j】表示区间【i,j】的某个值
写法个人偏爱记忆化的思路,递推形式也有两种,i表示起点j表示终点,或者是len表示区间的长度(从小到大),i表示起点计算终点j
初始化一般为dp【i】【i】
枚举中间点k,发现一个递推式子
或者是头和尾有某种匹配的时候,可以取最优
祝大家AC愉快!
最后,给大家推荐几个大神的区间Dp的学习地址:
http://www.cnblogs.com/ziyi--caolu/archive/2013/08/04/3236035.html