模板 - 动态规划 - 区间dp

因为昨天在Codeforces上设计的区间dp错了(错过了上紫的机会),觉得很难受。看看学长好像也有学,就不用看别的神犇的了。


区间dp处理环的时候可以把序列延长一倍。

下面是 $O(n^3)$ 的朴素区间dp:

for(int len = 1; len<=n; len++) { //枚举长度
    for(int i = 1; i+len<=n+1; i++) { //枚举起点
        int j = i+len - 1;
        for(int k = i; k<j; k++) { //枚举分割点,更新小区间最优解
            dp[i][j] = min(dp[i][j],dp[i][k]+dp[k+1][j]+cost[i][j]);
        }
    }
}

下面是四边形优化的 $O(n^2)$ 区间dp:

首先,使用四边形优化要满足下面的性质:

1.区间包含的单调性:

当小区间包含在大区间中,则小区间的成本不高于大区间的成本

2.四边形不等式:交叉小于包含

对于 $a<b≤c<d$ ,若 $f[a][c]+f[b][d]≤f[a][d]+f[c][d]$ ,则称 $f$ 满足四边形不等式。

定理:若能证明 $cost$ 满足1和2,则 $dp$ 也满足2。

定理:记 $s[i][j]$ 为 $dp[i][j]$ 取得最值时的分割点的下标 $k$ ,若 $dp$ 满足2,则 $s[i][j]$ 单调,也就是 $s[i][j]≤s[i][j+1]≤s[i+1][j+1]$ 。

应用上述结论:

 $dp[i][j]=min\{dp[i][k]+dp[k+1][j]\}+cost[i][j],s[i][j-1]≤k≤s[i+1][j]$

我们减少了k的枚举量,而k的枚举量为 $O(n^2)$。

而上述定理的证明……先省略吧……那我们只需要证明cost满足1和2,就可以使用四边形优化了。

另外要注意

s[i][i]=i;
for(int len = 2; len<=n; len++) { //枚举长度
    for(int i = 1; i+len<=n+1; i++) { //枚举起点
        int j = i+len - 1;
        dp[i][j]=INF;
        for(int k = s[i][j-1]; k<=s[i+1][j]; k++) { //枚举分割点,更新小区间最优解
            if(dp[i][k]+dp[k+1][j]<dp[i][j]){
                dp[i][j]=dp[i][k]+dp[k+1][j];
                s[i][j]=k;
            }
        }
        dp[i][j]+=cost[i][j];
    }
}

事先计算出 $cost[i][j]$ 就可以了。


来,开始看看学长搞了什么。

BZOJ 1260 涂色paint

https://www.lydsy.com/JudgeOnline/problem.php?id=1260

看了学长说的,设 $dp[i][j]$ 为把 $[i,j]$ 涂成指定颜色需要的最少cost,那么转移的时候怎么搞呢?


洛谷 P1880 石子合并

https://www.luogu.org/problemnew/show/P1880

#include<bits/stdc++.h>
using namespace std;
#define ll long long

int n;
int a[205];
int prefix[205];

int dp[205][205];

inline int sum(int l,int r) {
    return prefix[r]-prefix[l-1];
}

int main() {
    while(~scanf("%d",&n)) {
        memset(dp,0x3f,sizeof(dp));
        for(int i=1; i<=n; i++) {
            scanf("%d",&a[i]);
            prefix[i]=prefix[i-1]+a[i];
            dp[i][i]=0;
        }

        for(int i=n+1; i<=2*n; i++) {
            a[i]=a[i-n];
            prefix[i]=prefix[i-1]+a[i];
            dp[i][i]=0;
        }


        for(int len = 1; len<=n; len++) { //枚举长度
            for(int i = 1; i+len-1<=2*n; i++) { //枚举起点
                int j = i+len - 1;
                for(int k = i; k<j; k++) { //枚举分割点,更新小区间最优解
                    dp[i][j] = min(dp[i][j],dp[i][k]+dp[k+1][j]+sum(i,j));
                    //printf("k=%d\n",k);
                }
                //printf("sum(i,j)=%d dp[%d][%d]=%d\n",sum(i,j),i,j,dp[i][j]);
            }
        }

        int ans=0x3f3f3f3f;
        for(int i=1;i<=n;i++){
            ans=min(ans,dp[i][i+n-1]);
        }
        printf("%d\n",ans);

        memset(dp,0,sizeof(dp));
        for(int len = 1; len<=n; len++) { //枚举长度
            for(int i = 1; i+len-1<=2*n; i++) { //枚举起点
                int j = i+len - 1;
                for(int k = i; k<j; k++) { //枚举分割点,更新小区间最优解
                    dp[i][j] = max(dp[i][j],dp[i][k]+dp[k+1][j]+sum(i,j));
                }
            }
        }

        ans=0;
        for(int i=1;i<=n;i++){
            ans=max(ans,dp[i][i+n-1]);
        }
        printf("%d\n",ans);

    }
}
View Code

水题,记得要扩大一倍。

使用四边形不等式时,运算必须是最小值,最大值不满足单调性,如下。

#include<bits/stdc++.h>
using namespace std;
#define ll long long

int n;
int a[205];
int prefix[205];

int dp[205][205];
int dp2[205][205];
int s[205][205];

inline int sum(int l,int r) {
    return prefix[r]-prefix[l-1];
}

int main() {
    while(~scanf("%d",&n)) {
        memset(dp,0x3f,sizeof(dp));
        memset(dp2,0x3f,sizeof(dp2));

        for(int i=1; i<=n; i++) {
            scanf("%d",&a[i]);
            prefix[i]=prefix[i-1]+a[i];
            dp[i][i]=0;
            dp2[i][i]=0;
            s[i][i]=i;
        }

        for(int i=n+1; i<=2*n; i++) {
            a[i]=a[i-n];
            prefix[i]=prefix[i-1]+a[i];
            dp[i][i]=0;
            dp2[i][i]=0;
            s[i][i]=i;
        }

        for(int len = 2; len<=n; len++) { //枚举长度
            for(int i = 1; i+len<=2*n+1; i++) { //枚举起点
                int j = i+len - 1;
                for(int k = s[i][j-1]; k<=s[i+1][j]; k++) { //枚举分割点,更新小区间最优解
                    if(dp[i][k]+dp[k+1][j]<dp[i][j]){
                        dp[i][j]=dp[i][k]+dp[k+1][j];
                        s[i][j]=k;
                    }
                    //printf("k=%d\n",k);
                }

                dp[i][j]+=sum(i,j);
                //printf("dp[%d][%d]=%d\n",i,j,dp[i][j]);
            }
        }

        for(int len = 1; len<=n; len++) { //枚举长度
            for(int i = 1; i+len-1<=2*n; i++) { //枚举起点
                int j = i+len - 1;
                for(int k = i; k<j; k++) { //枚举分割点,更新小区间最优解
                    dp2[i][j] = min(dp2[i][j],dp2[i][k]+dp2[k+1][j]+sum(i,j));
                    //printf("k=%d\n",k);
                }
                if(dp[i][j]!=dp2[i][j])
                    printf("sum(i,j)=%d dp[%d][%d]=%d dp2[%d][%d]=%d\n",sum(i,j),i,j,dp[i][j],i,j,dp2[i][j]);
            }
        }

        int ans=0x3f3f3f3f;
        for(int i=1;i<=n;i++){
            ans=min(ans,dp[i][i+n-1]);
        }
        printf("%d\n",ans);


        for(int i=1; i<=2*n; i++) {
            s[i][i]=i;
        }

        memset(dp,0,sizeof(dp));
        memset(dp2,0,sizeof(dp2));

        for(int len = 2; len<=n; len++) { //枚举长度
            for(int i = 1; i+len<=2*n+1; i++) { //枚举起点
                int j = i+len - 1;
                for(int k = s[i][j-1]; k<=s[i+1][j]; k++) { //枚举分割点,更新小区间最优解
                    if(dp[i][k]+dp[k+1][j]>dp[i][j]){
                        dp[i][j]=dp[i][k]+dp[k+1][j];
                        s[i][j]=k;
                    }
                }
                dp[i][j]+=sum(i,j);
            }
        }

        for(int len = 1; len<=n; len++) { //枚举长度
            for(int i = 1; i+len-1<=2*n; i++) { //枚举起点
                int j = i+len - 1;
                for(int k = i; k<j; k++) { //枚举分割点,更新小区间最优解
                    dp2[i][j] = max(dp2[i][j],dp2[i][k]+dp2[k+1][j]+sum(i,j));
                    //printf("k=%d\n",k);
                }
                if(i<10&&j<10&&dp[i][j]!=dp2[i][j])
                    printf("sum(i,j)=%d dp[%d][%d]=%d dp2[%d][%d]=%d\n",sum(i,j),i,j,dp[i][j],i,j,dp2[i][j]);
            }
        }

        ans=0;
        for(int i=1;i<=n;i++){
            ans=max(ans,dp[i][i+n-1]);
        }
        printf("%d\n",ans);

    }
}
View Code

猜你喜欢

转载自www.cnblogs.com/Yinku/p/10486446.html