zoukankan      html  css  js  c++  java
  • 石子合并问题,经典区间DP

    一、直线型

    问题描述: 

      有N堆石子排成一排,每堆石子有一定的数量。现要将N堆石子并成为一堆。合并的过程只能每次将相邻的两堆石子堆成一堆,每次合并花费的代价为这两堆石子的和,经过N-1次合并后成为一堆。求出总的代价最小值(或最大值)。

    思路: 设 DP[ i ][ j ] 表示第 i 堆合并到第 j 堆的代价的最小值(或最大值),然后,我们假设,sum[ i ] 为前 i 堆石子的总和,即前缀和。

           然后就可以枚举区间长度,枚举中间点, 进行DP了, 复杂度o(n^3)

       dp[ i ][ j ] = 0;                                                i = j;

       dp[ i ][ j ] = dp[ i ][ k ] + dp[ k + 1 ][ j ] + sum[ j ] - sum[ i - 1 ];         i != j; i <= k < j;

    #include <bits/stdc++.h>
    #define LL long long
    #define mem(i, j) memset(i, j, sizeof(i))
    #define rep(i, j, k) for(LL i = j; i <= k; i++)
    #define dep(i, j, k) for(int i = k; i >= j; i--)
    #define pb push_back
    #define make make_pair
    #define INF INT_MAX
    #define inf LLONG_MAX
    using namespace std;
    
    const int N = 1e3 + 5;
    
    int dp[N][N], sum[N];
    
    int main() {
        int n; scanf("%d", &n);
        rep(i, 1, n) {
            int x; scanf("%d", &x);
            sum[i] = sum[i - 1] + x;
            dp[i][i] = 0;
        }
        rep(len, 1, n) {
            rep(i, 1, n - len) {
                int j = i + len;
                dp[i][j] = INF;
                rep(k, i, j - 1) {
                    dp[i][j] = min(dp[i][j], dp[i][k] + dp[k + 1][j] + sum[j] - sum[i - 1]);
                }
            }
        }
        printf("%d
    ", dp[1][n]);
    }
    View Code

    平行四边形优化

    用 p[ i ][ j ] 表示区间 [ i, j ]的最优分割点, 这样, 我们就可将找最优分割点的那个地方,优化一下了。

    本来我们需要枚举, [ i, j - 1 ] 现在只需要枚举 [ p[ i ][ j - 1] , p[ i + 1 ][ j ] ] 就行了。

    关于这个优化的证明 -> 这个  博客 

    时间复杂度,接近o(n^2)

    #include <cstdio>
    #include <string>
    #include <cstring>
    #include <algorithm>
    #define LL long long
    #define mem(i, j) memset((i), (j), sizeof(i))
    #define rep(i, j, k) for(LL i = j; i <= k; i++)
    #define dep(i, j, k) for(int i = k; i >= j; i--)
    #define pb push_back
    #define make make_pair
    #define INF 0x3f3f3f3f
    #define inf 0x3f3f3f3f3f3f
    using namespace std;
    
    const int N = 2e3 + 5;
    
    int dp[N][N], sum[N], p[N][N], a[N];
    
    int main() {
        int n;
        while(~scanf("%d", &n)) {
            mem(dp, 0x3f); sum[0] = 0;
            rep(i, 1, n) {
                scanf("%d", &a[i]);
                sum[i] = sum[i - 1] + a[i];
                dp[i][i] = 0; p[i][i] = i;
            }
            rep(len, 2, n) {
                rep(i, 1, n - 1) {
                    int j = i + len - 1;
                    if(j > n) break;
                    rep(k, p[i][j-1], p[i + 1][j]) {
                        if(dp[i][k] + dp[k + 1][j] + sum[j] - sum[i - 1] < dp[i][j]) {
                            dp[i][j] = dp[i][k] + dp[k + 1][j] + sum[j] - sum[i - 1];
                            p[i][j] = k;
                        }
                    }
                }
            }
            printf("%d
    ", dp[1][n]);
        }
        return 0;
    }
    View Code

    还有一个算法可以解决这种问题,  GarsiaWachs算法  针对数据大的,开不了二维数组的, 复杂度也是 o(n^2)

    二、环型

     和上面题型类似, 就是这里的石子堆是围成一个环的,其他条件一致,还是求代价的最小值(或最大)。

    思路: 其实也不难, 就把, a[ 1 ] ~ a[ n - 1] 复制一份,存到a[ n + 1 ] ~ a[ n + n - 1 ] 然后, 还是像上面一样做。

          最终答案就是, min(dp[ i ][ i + n - 1 ])  1 <= i <= n;

    #include <cstdio>
    #include <string>
    #include <cstring>
    #include <algorithm>
    #define LL long long
    #define mem(i, j) memset((i), (j), sizeof(i))
    #define rep(i, j, k) for(LL i = j; i <= k; i++)
    #define dep(i, j, k) for(int i = k; i >= j; i--)
    #define pb push_back
    #define make make_pair
    #define INF 0x3f3f3f3f
    #define inf 0x3f3f3f3f3f3f
    using namespace std;
    
    const int N = 2e3 + 5;
    
    int dp[N][N], sum[N], p[N][N], a[N];
    
    int main() {
        int n;
        while(~scanf("%d", &n)) {
            mem(dp, 0x3f); sum[0] = 0;
            rep(i, 1, n) {
                scanf("%d", &a[i]);
                sum[i] = sum[i - 1] + a[i];
                dp[i][i] = 0; p[i][i] = i;
            }
            rep(i, 1, n - 1) {
                sum[i + n] = sum[i + n - 1] + a[i];
                p[i + n][i + n] = i + n;
                dp[i + n][i + n] = 0;
            }
            rep(len, 2, n) {
                rep(i, 1, (2 * n) - 1) {
                    int j = i + len - 1;
                    if(j > 2 * n - 1) break; 
                    rep(k, p[i][j-1], p[i + 1][j]) {
                        if(dp[i][k] + dp[k + 1][j] + sum[j] - sum[i - 1] < dp[i][j]) {
                            dp[i][j] = dp[i][k] + dp[k + 1][j] + sum[j] - sum[i - 1];
                            p[i][j] = k;
                        }
                    }
                }
            }
            int ans = INF;
            rep(i, 1, n) ans = min(ans, dp[i][i + n - 1]);
            printf("%d
    ", ans);
        }
        return 0;
    }
    View Code
    一步一步,永不停息
  • 相关阅读:
    两数之和等于目标值
    Atitit.软件仪表盘(0)--软件的子系统体系说明
    获取 exception 对象的字符串形式(接口服务返回给调用者)
    SELECT LAST_INSERT_ID() 的使用和注意事项
    @RequestParam 注解的使用
    Eclipse中修改SVN用户名和密码方法
    淘淘商城上传图片按钮不显示的问题
    spring集成webSocket实现服务端向前端推送消息
    Mybatis中jdbcType和javaType对应关系
    MySQL数据库中tinyint字段值为1,读取出来为true的问题
  • 原文地址:https://www.cnblogs.com/Willems/p/12251767.html
Copyright © 2011-2022 走看看