洛谷 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
还有一个问题就是,题目给出来的石头是链装的
只要“断环为链”即复制一份黏到末尾就行了
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
#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\n%d\n", MM, MX); return 0; }