洛谷 P1080 石子合并 ( 区间DP )

题意 : 在一个圆形操场的四周摆放N堆石子,现要将石子有次序地合并成一堆.规定每次只能选相邻的2堆合并成新的一堆,并将新的一堆的石子数,记为该次合并的得分。试设计出1个算法,计算出将N堆石子合并成1堆的最小得分和最大得分。

分析 : 

有大佬给出了四边形不等式优化........

发现并不会,于是开始学习区间DP的写法

对于某一个特定的区间 (i, j) 其合并成一个石子是从

某两个属于这个区间且连续不相交区间合并而来

即假设有断点 k 则 (i, j) = (i, k) + (k+1, j) + Sum(i, j)

这里定义 Sum(i, j) 为 i 到 j 这个区间石子的总和、前缀和可实现

只要枚举断点就可以知道 (i, j) 的最优值是多少了

定义 dp[i][j] = 区间 i 到 j 的石子合并代价的最值

转移方程就是枚举断点 dp[i][j] = dp[i][k] + dp[k+1][j] + Sum(i, j)

但是这个有一个坑,如果你用三重循环,分别表示

区间开头 i、区间结尾 j、区间断点 k 来进行 状态转移

这样是错误的,因为 dp[k+1][j] 在这样的循环下是还未确定的

所以有个技巧就是将一重循环变成区间长度,即

区间长度 len、区间开头 i、区间断点 k

还有一个问题就是,题目给出来的石头是链装的

只要“断环为链”即复制一份黏到末尾就行了

#include<bits/stdc++.h>
using namespace std;
const int INF = 0x3f3f3f3f;
const int maxn = 2e2 + 10;
int dp1[maxn][maxn], dp2[maxn][maxn];
int PreSum[maxn];
int arr[maxn];
int N;

int main(void)
{
    scanf("%d", &N);

    memset(dp1, 0, sizeof(dp1));
    memset(dp2, 0, sizeof(dp2));
    memset(PreSum, 0, sizeof(PreSum));

    for(int i=1; i<=N; i++){
        scanf("%d", &arr[i]);
        arr[i+N] = arr[i];
    }

    for(int i=1; i<=(N<<1); i++)
        PreSum[i] = PreSum[i-1] + arr[i];

    for(int len=2; len<=N; len++){
        for(int i=1; i<=(N<<1)-len+1; i++){
            int j = i + len - 1;
            int MX = 0, MM = INF;
            for(int k=i; k<j; k++){
                MM = min(MM, dp1[i][k]+dp1[k+1][j]+PreSum[j]-PreSum[i-1]);
                MX = max(MX, dp2[i][k]+dp2[k+1][j]+PreSum[j]-PreSum[i-1]);
            }
            dp1[i][j] = MM;
            dp2[i][j] = MX;
        }
    }

    int MX = 0, MM = INF;
    for(int i=1; i<=N; i++){
        MM = min(MM, dp1[i][i+N-1]);
        MX = max(MX, dp2[i][i+N-1]);
    }

    printf("%d
%d
", MM, MX);
    return 0;
}
View Code
原文地址:https://www.cnblogs.com/qwertiLH/p/8906817.html