zoukankan      html  css  js  c++  java
  • 模板

    因为昨天在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
    ",k);
                    }
                    //printf("sum(i,j)=%d dp[%d][%d]=%d
    ",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
    ",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
    ",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
    ",k);
                    }
    
                    dp[i][j]+=sum(i,j);
                    //printf("dp[%d][%d]=%d
    ",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
    ",k);
                    }
                    if(dp[i][j]!=dp2[i][j])
                        printf("sum(i,j)=%d dp[%d][%d]=%d dp2[%d][%d]=%d
    ",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
    ",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
    ",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
    ",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
    ",ans);
    
        }
    }
    View Code
  • 相关阅读:
    《深度探索C++对象模型》1
    《C++标准库》
    关于多级分类的封装
    git常用命令
    使用BigDecimal进行精确运算
    关于强制装换
    page分页
    pageContext.request.contextPath 和 request.getContextPath()
    springMVC + mybatis 搜索 分页等
    mybatis 动态sql
  • 原文地址:https://www.cnblogs.com/Yinku/p/10486446.html
Copyright © 2011-2022 走看看