石子合并问题,经典区间DP

Posted willems

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了石子合并问题,经典区间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

 

 

二、环型

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

 

思路: 其实也不难, 就把, 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

 

以上是关于石子合并问题,经典区间DP的主要内容,如果未能解决你的问题,请参考以下文章

石子合并2——区间DP这是道经典入门例题/试手模板

[NYIST737]石子合并(区间dp)

区间 dp

区间DP

区间型DP

从合并石子学区间DP