[kuangbin带你飞]专题二十二 区间DP

A        zoj 3537

B        LightOJ 1422

dp[i][j] d p [ i ] [ j ] 为区间 [i,j] [ i , j ] 的最小花费

边界条件 dp[i][i]=1 d p [ i ] [ i ] = 1

考虑区间 [i,j] [ i , j ] ,对于衣服 i i ,最差情况下,[i+1][j]中不将这件衣服重复利用,那么有

dp[i][j]=dp[i+1][j]+1 d p [ i ] [ j ] = d p [ i + 1 ] [ j ] + 1

而实际上,假设 [i+1,j] [ i + 1 , j ] 中存在衣服 k k 与衣服i相同,那么可以尝试用 i i 代替k,则有

dp[i][j]=min(dp[i][j],dp[i][k1]+dp[k+1][j])(i+1kj,c[i]==c[k]) d p [ i ] [ j ] = m i n ( d p [ i ] [ j ] , d p [ i ] [ k − 1 ] + d p [ k + 1 ] [ j ] ) ( i + 1 ≤ k ≤ j , c [ i ] == c [ k ] )

注意dp顺序为从后往前,小区间到大区间

代码:

#include <iostream>
#include <cstdio>
#include <cstring>
using namespace std;
const int maxn=110;
int T,n,c[maxn];
int dp[maxn][maxn];

int main(){
    scanf("%d",&T);
    for(int t=1;t<=T;t++){
        memset(dp,0,sizeof(dp));
        scanf("%d",&n);
        for(int i=1;i<=n;i++){
            scanf("%d",&c[i]);
            dp[i][i]=1;
        }
        for(int i=2;i<=n;i++)
            for(int j=n;j-i+1>=1;j--){
                dp[j-i+1][j]=dp[j-i+2][j]+1;
                for(int k=j-i+2;k<=j;k++)
                    if(c[k]==c[j-i+1]) dp[j-i+1][j]=min(dp[j-i+1][j],dp[j-i+1][k-1]+dp[k+1][j]);
            }
        printf("Case %d: %d\n",t,dp[1][n]);
    }
    return 0;
}

C        poj 2955

dp[i][j] d p [ i ] [ j ] [i,j] [ i , j ] 内最大子序列长度

先预处理所有 dp[i][i+1] d p [ i ] [ i + 1 ] ,然后

1.若 a[i] a [ i ] a[j] a [ j ] 可以匹配,显然 dp[i][j] d p [ i ] [ j ] 可以取

dp[i][j]=dp[i+1][j1]+2 d p [ i ] [ j ] = d p [ i + 1 ] [ j − 1 ] + 2

2.对于所有 dp[i][j] d p [ i ] [ j ] ,都有

dp[i][j]=max(dp[i][j],dp[i][k]+dp[k+1][j])(ik<j) d p [ i ] [ j ] = m a x ( d p [ i ] [ j ] , d p [ i ] [ k ] + d p [ k + 1 ] [ j ] ) ( i ≤ k < j )

即两部分合并得到 dp[i][j] d p [ i ] [ j ]

这里合并不需要考虑跨越两部分的括号匹配,因为这种情况在 [i,j] [ i , j ] 的子区间中已经按1.的方式统计过了

代码:

#include <iostream>
#include <cstdio>
#include <cstring>
using namespace std;
const int maxn=110;
char a[maxn];
int n,dp[maxn][maxn];

int main(){
    while(1){
        memset(dp,0,sizeof(dp));
        scanf("%s",a);
        if(a[0]=='e') break;
        n=strlen(a);
        for(int i=0;i<n-1;i++)
            if((a[i]=='('&&a[i+1]==')')||(a[i]=='['&&a[i+1]==']')) dp[i][i+1]=2;
        for(int i=3;i<=n;i++)
            for(int j=0;j+i-1<n;j++){
                if((a[j]=='('&&a[j+i-1]==')')||(a[j]=='['&&a[j+i-1]==']')) dp[j][j+i-1]=dp[j+1][j+i-2]+2;
                for(int k=j;k<j+i-1;k++)
                    dp[j][j+i-1]=max(dp[j][j+i-1],dp[j][k]+dp[k+1][j+i-1]);
            }
        printf("%d\n",dp[0][n-1]);
    }
    return 0;
}

D        CF 149D

https://www.luogu.org/blog/hhz6830975/cf149d-coloring-brackets-ou-jian-dp-ji-yi-hua-sou-suo-post

E        poj 1651

dp[i][j] d p [ i ] [ j ] 为将区间 [i,j] [ i , j ] 全部取出的最小值(包括 a[i] a [ i ] a[j] a [ j ] ,两端按原来的 a[i1] a [ i − 1 ] a[j+1] a [ j + 1 ] 算)

边界条件 dp[i][i]=a[i1]a[i]a[i+1] d p [ i ] [ i ] = a [ i − 1 ] ⋅ a [ i ] ⋅ a [ i + 1 ]

对于 dp[i][j] d p [ i ] [ j ] ,设 [i,j] [ i , j ] 中最后取出的为 a[k] a [ k ] ,那么dp方程为:

dp[i][j]=min(dp[i][k1]+dp[k+1][j]+a[i1]a[k]a[j+1])(ikj) d p [ i ] [ j ] = m i n ( d p [ i ] [ k − 1 ] + d p [ k + 1 ] [ j ] + a [ i − 1 ] ⋅ a [ k ] ⋅ a [ j + 1 ] ) ( i ≤ k ≤ j )

代码:

#include <iostream>
#include <cstdio>
#include <cstring>
using namespace std;
const int maxn=110;
const int INF=0x7fffffff;
int n,a[maxn],dp[maxn][maxn];

int main(){
    cin>>n;
    for(int i=1;i<=n;i++)
        cin>>a[i];
    for(int i=1;i<=n-2;i++)
        for(int j=2;j+i-1<n;j++){
            dp[j][j+i-1]=INF;
            for(int k=j;k<=j+i-1;k++)
                dp[j][j+i-1]=min(dp[j][j+i-1],dp[j][k-1]+dp[k+1][j+i-1]+a[j-1]*a[k]*a[j+i]);
        }
    cout<<dp[2][n-1]<<endl;
    return 0;
}

F        zoj 3469

有一道类似的题,洛谷试炼场的P1220

这题的区别在于,每个点单位时间的费用是不同的,这就造成了存在后效性的问题

其实简单处理一下就可以了,只需要在每次转移的时候将未到过的点的费用一起算上

于是得到dp方程:

dp[i][j][0]=min(dp[i][j][0],dp[i+1][j][0]+(sumB[i]+sumB[n]sumB[j])(x[i+1]x[i])v) d p [ i ] [ j ] [ 0 ] = m i n ( d p [ i ] [ j ] [ 0 ] , d p [ i + 1 ] [ j ] [ 0 ] + ( s u m B [ i ] + s u m B [ n ] − s u m B [ j ] ) ⋅ ( x [ i + 1 ] − x [ i ] ) ⋅ v )

dp[i][j][0]=min(dp[i][j][0],dp[i+1][j][1]+(sumB[i]+sumB[n]sumB[j])(x[j]x[i])v) d p [ i ] [ j ] [ 0 ] = m i n ( d p [ i ] [ j ] [ 0 ] , d p [ i + 1 ] [ j ] [ 1 ] + ( s u m B [ i ] + s u m B [ n ] − s u m B [ j ] ) ⋅ ( x [ j ] − x [ i ] ) ⋅ v )

dp[i][j][1]=min(dp[i][j][1],dp[i][j1][0]+(sumB[i1]+sumB[n]sumB[j1])(x[j]x[i])v) d p [ i ] [ j ] [ 1 ] = m i n ( d p [ i ] [ j ] [ 1 ] , d p [ i ] [ j − 1 ] [ 0 ] + ( s u m B [ i − 1 ] + s u m B [ n ] − s u m B [ j − 1 ] ) ⋅ ( x [ j ] − x [ i ] ) ⋅ v )

dp[i][j][1]=min(dp[i][j][1],dp[i][j1][1]+(sumB[i1]+sumB[n]sumB[j1])(x[j]x[j1])v) d p [ i ] [ j ] [ 1 ] = m i n ( d p [ i ] [ j ] [ 1 ] , d p [ i ] [ j − 1 ] [ 1 ] + ( s u m B [ i − 1 ] + s u m B [ n ] − s u m B [ j − 1 ] ) ⋅ ( x [ j ] − x [ j − 1 ] ) ⋅ v )

G        hdu 4283

dp[i][j] d p [ i ] [ j ] 表示假设前面没有人,区间 [i,j] [ i , j ] 的最小值

对于 i i 这个人,考虑让他调到k的位置出场,那么就得到dp方程:

dp[i][j]=min{dp[i+1][k]+dp[k+1][j]+(sum[i]sum[i1])(ki)+(sum[j]sum[k])(k+1i)}(ikj) d p [ i ] [ j ] = m i n { d p [ i + 1 ] [ k ] + d p [ k + 1 ] [ j ] + ( s u m [ i ] − s u m [ i − 1 ] ) ⋅ ( k − i ) + ( s u m [ j ] − s u m [ k ] ) ⋅ ( k + 1 − i ) } ( i ≤ k ≤ j )

为什么这样能保证改变顺序的人满足栈的顺序?

所谓栈的顺序,其实就是要求对于两个同时存在于栈中的人 x x y,若 x<y x < y ,那么改变后的位置必须满足 x>y x ′ > y ′

在上面的方程中,设 x=i x = i ,则 x=k x ′ = k

对于 y y ,分类讨论:

1.若y[i+1,k],我们在之前计算 dp[i+1][k] d p [ i + 1 ] [ k ] 的子问题中,一定包含了计算 dp[y][k] d p [ y ] [ k ] 的子问题,也就默认 y[y,k] y ′ ∈ [ y , k ] x x 改变后为[y1,k1]),一定在 x x ′ 前面,符合要求

2.若 y>k y > k ,同理有 y>x y ′ > x ′ x x 此时已出栈,所以也是符合要求的

代码:

#include <iostream>
#include <cstdio>
#include <cstring>
using namespace std;
const int maxn=110;
const int INF=0x7fffffff;
int T,t=1,n,sum[maxn],dp[maxn][maxn];

int main(){
    scanf("%d",&T);
    while(t<=T){
        scanf("%d",&n);
        for(int i=1;i<=n;i++){
            scanf("%d",&sum[i]);
            dp[i][i]=0,sum[i]+=sum[i-1];
        }
        for(int l=2;l<=n;l++)
            for(int i=1,j=i+l-1;j<=n;i++,j++){
                dp[i][j]=INF;
                for(int k=i;k<=j;k++)
                    dp[i][j]=min(dp[i][j],dp[i+1][k]+dp[k+1][j]+(sum[i]-sum[i-1])*(k-i)+(sum[j]-sum[k])*(k+1-i));
            }
        printf("Case #%d: %d\n",t++,dp[1][n]);
    }
    return 0;
}

H  hdu 2476

先假设两个字符串完全不相同, dp[i][j] d p [ i ] [ j ] 表示将a串 [i,j] [ i , j ] 部分变成b串的最少次数

显然最差情况为单独刷,有:

dp[i][j]=min{dp[i][k]+dp[k+1][j]}(ikj) d p [ i ] [ j ] = m i n { d p [ i ] [ k ] + d p [ k + 1 ] [ j ] } ( i ≤ k ≤ j )

若区间中存在 k k ,使得a[i]=a[k],那么可以先将 [i,k] [ i , k ] 刷成 a[i] a [ i ] ,再刷 [i+1,k] [ i + 1 , k ] (刷 [i,k] [ i , k ] 的一次在 dp[i+1][k] d p [ i + 1 ] [ k ] 中刷 k k 的时候已经计算了),那么有:

dp[i][j]=min{dp[i+1][k]+dp[k+1][j]}(ikj)

这样得到的dp数组是假设两个字符串完全不相同时得到的,还需要进一步处理

ans[i] a n s [ i ] 为实际刷区间 [0,i] [ 0 , i ] 需要的次数

如果 a[i]=b[i] a [ i ] = b [ i ] ,那么 i i 不用再刷,有:

ans[i]=ans[i1]

如果不等,那么:

ans[i]=min{ans[j]+dp[j+1][k]}(0j<i) a n s [ i ] = m i n { a n s [ j ] + d p [ j + 1 ] [ k ] } ( 0 ≤ j < i )

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值