区间DP思路和题型总结

区间DP的总结和整理

基本思路

  • 确定状态表示,即表示的状态是什么

  • 确定划分依据,即的含义和取值(是否能取到L或者R)

  • 基本框架(枚举区间长度和左端点,并确定右端点,通过k进行转移)

    for (int len = 2;len <= n;++len){
        for (int i = 1;i + len - 1 <= n;++i){
            int r = i + len - 1;int l = i;
            f[l][r] = INF;
            for (int k = l;k < r;++k)
                f[l][r] = min(f[l][r],f[l][k] + f[k+1][r] + s[r] - s[l-1]);
        }
    }
    cout << f[1][n] << endl;

基本题型

  • 石子合并

  • 环形石子合并

    将总区间扩展成两端(长度为2n),然后直接转化为线性的区间dp

    int a[N],s[N];
    int f[N][N],g[N][N];
    int n;
    int main(){
        cin >> n;
        for (int i = 1;i <= n;++i){
            cin >> a[i];
            a[i+n] = a[i];
        }
        for (int i = 1;i <= n*2;++i){
            s[i] = s[i-1] + a[i];
        }
    
        memset(f,0x3f,sizeof f);
        memset(g,-0x3f,sizeof g);
        for (int len = 1;len <= n * 2;++len){
            for (int i = 1;i + len - 1 <= n * 2;++i){
                int l = i,r =  i + len - 1;
                if (l == r)
                    f[l][r] = g[l][r] = 0;
                for (int k = l;k < r;++k){
                    f[l][r] = min(f[l][r],f[l][k] + f[k+1][r] + s[r] - s[l-1]);
                    g[l][r] = max(g[l][r],g[l][k] + g[k+1][r] + s[r] - s[l-1]);
                }
            }
        }
    
        int maxn = -INF,minn = INF;
        for (int i = 1;i <= n;++i){
            maxn = max(maxn,g[i][i+n-1]);
            minn = min(minn,f[i][i+n-1]);
        }
        cout << minn << endl << maxn << endl;
        return 0;
    }
  • 能量项链(同上),注意k的取值和最后答案的选取

  • 加分二叉树,这题比较特别,​选取的是数上所有的根节点,f[l][k-1]为左子树的得分,f[k+1][r]为右子树的得分,每次转移计算根节点的得分。

    void dfs(int l,int r){
        if (l > r) return;
        int root = g[l][r];
        cout << root << " ";
        dfs(l,root-1);
        dfs(root + 1,r);
    }
    
    int main(){
        cin >> n;
        for (int i = 1;i <= n;++i)   cin >> a[i];
    
        for (int len = 1;len <= n;++len){
            for (int l = 1;l + len - 1 <= n;++l){
                int r = l + len - 1;
                if (l == r){
                    g[l][r] = l;
                    f[l][r] = a[l];
                }
                else{
                    for (int k = l;k <= r;++k){
                        int left = k == l ? 1 : f[l][k-1];
                        int right = k == r ? 1 : f[k+1][r];
                        int s = left * right + a[k];
                        if (f[l][r] < s){
                            f[l][r] = s;
                            g[l][r] = k;
                        }
                    }
                }
            }
        }
        cout << f[1][n] << endl;
        dfs(1,n);
        puts("");
        return 0;
    }
  • 凸多边形的划分(与能量项链类似,注意数据范围)

    inline __int128 read(){
        __int128 x = 0, f = 1;
        char ch = getchar();
        while(ch < '0' || ch > '9'){
            if(ch == '-')
                f = -1;
            ch = getchar();
        }
        while(ch >= '0' && ch <= '9'){
            x = x * 10 + ch - '0';
            ch = getchar();
        }
        return x * f;
    }
    inline void print(__int128 x){
        if(x < 0){
            putchar('-');
            x = -x;
        }
        if(x > 9)
            print(x / 10);
        putchar(x % 10 + '0');
    }
    
    int n;
    __int128 f[N][N];
    __int128 a[N];
    int main(){
        cin >> n;
        for (int i = 1;i <= n;++i)   a[i] = read();
    
        for (int len = 3;len <= n;++len){
            for (int l = 1;l + len - 1 <= n;++l){
                int r = l + len - 1;
                f[l][r] = INF;
                for (int k = l + 1;k < r;++k){
                    //if (a[l] * a[k] * a[r] < 0)    puts("yes");
                    f[l][r] = min(f[l][r],f[l][k] + f[k][r] + (a[l] * a[k] * a[r]));
                }
            }
        }
        print(f[1][n]);
        puts("");
        return 0;
    }
  • 棋盘划分,二位的区间dp,区间的定义变为了矩形而不是线性的,参照二位前缀和的定义

    int n,m;
    double f[M][M][M][M][N];
    int s[N][N];
    double X;
    
    double get(int x1,int y1,int x2,int y2){
        double res = s[x2][y2] - s[x2][y1-1] - s[x1-1][y2] + s[x1-1][y1-1] - X;
        return res * res /  n;
    }
    
    double dp(int x1,int y1,int x2,int y2,int k){
        double &v = f[x1][x2][y1][y2][k];
        if (v >= 0)  return v;
        if (k == 1) return v = get(x1,y1,x2,y2);
    
        v = INF;
        for (int i = x1;i < x2;++i){
            v = min(v,dp(x1,y1,i,y2,k-1) + get(i+1,y1,x2,y2));
            v = min(v,dp(i+1,y1,x2,y2,k-1) + get(x1,y1,i,y2));
        }
    
        for (int j = y1;j < y2;++j){
            v = min(v,dp(x1,y1,x2,j,k-1) + get(x1,j + 1,x2,y2));
            v = min(v,dp(x1,j + 1,x2,y2,k-1) + get(x1,y1,x2,j));
        }
        return v;
    }
    
    int main(){
        cin >> n;
        for (int i = 1;i <= 8;++i){
            for (int j = 1;j <= 8;++j){
                cin >> s[i][j];
                s[i][j] += s[i-1][j] + s[i][j-1] - s[i-1][j-1];
            }
        }
        X = (double) s[8][8] / n;
        memset(f,-1,sizeof f);
        printf("%.3lf\n",sqrt(dp(1,1,8,8,n)));
        return 0;
    }
全部评论

相关推荐

ALEX_BLX:虽然说聊天记录不可信,不过这个趋势确实如此但我觉得也要想到一点就是卷后端的人里真正有“料”的人又有多少,我说的这个料都不是说一定要到大佬那种级别,而是就一个正常的水平。即使是现在也有很多人是跟风转码的,2-3个月速成后端技术栈的人数不胜数,但今时不同往日没可能靠速成进大厂了。这种情况就跟考研一样,你能上考场就已经打败一半的人了
点赞 评论 收藏
分享
评论
点赞
收藏
分享

创作者周榜

更多
牛客网
牛客企业服务