2017 ACM-ICPC亚洲区域赛北京站J题 Pangu and Stones 题解 区间DP
题目链接:http://www.hihocoder.com/problemset/problem/1636
题目描述
在中国古代神话中,盘古是时间第一个人并且开天辟地,它从混沌中醒来并把混沌分为天地。
刚开始地上是没有山的,只有满地的石头。
这里有 \(N\) 堆石头,标号为从 \(1\) 到 \(N\) 。盘古想要把它们合成一堆建造一座大山。如果某些堆石头的数量总和是 \(S\) ,盘古需要 \(S\) 秒才能把它们合成一堆,这新的一堆石头的数量就是 \(S\) 。
不幸的是,每一次盘古只能把连续的 \(L\) 到 \(R\) 堆石头合并成一堆。
盘古希望尽快把所有石头合成一堆。
你能帮帮他吗?如果没有解,则输出 \(0\) 。
解题思路
可以把合并所有石头的过程拆分成几个子步骤,首先合并连续的一些,然后再合并连续的一些,大区间的结果可以由小区间推出,所以就从小区间开始考虑,逐步推向大区间,可以用 dp[i][j][k]
表示区间 [i,j]
分成 k
堆得最小代价,对于固定的一个区间,肯定是取所有情况的最小值,最后答案是 dp[1][n][1]
,注意边界处理,包括刚开始的初始化。
然后就是代码处理中的一些细节了。
首先将所有的 f[i][j][k]
置为 INF
。
然后对于所有的初始状态(即 f[i][j][j-i+1]
)都置为 \(0\) 。
因为区间 \([i,j]\) 内本身就有 \(j-i+1\) 个元素,所以这些元素本身就这么多堆,是不需要花费代价去划分的。这就是我所说的初始状态。
然后就是合并区间了,区间合并一般都是小区间开始合并到大区间,我们这里也不例外(记忆化搜索的话就得反着来了)。
对于一个区间 \([l,r]\) ,它要么是直接合并成一对,要么是若干个区间拼接到一起。所以我们这里分情况讨论:
直接合并
如果区间 \([l,r]\) 直接合并,那么合并成一对的最小代价应该是 \(f[l][r][1]\) 。
那么对于区间 \([l,r]\) ,我一定可以将其查分成两个区间 \([l,i]\) 和 \([i+1,r]\) ,其中坐区间有 \(j\) 个元素,右区间有 \(1\) 个元素,并且满足 \(L \le j+1 \le R\) 。
于是我们可以从 \(l \sim r-1\) 枚举 \(i\) ,从 \(L-1 \sim R-1\) 枚举 \(j\) ,
则 \(f[l][r][1] = \min(f[l][i][j]+f[i+1][r][1]) + sum[r] - sum[l-1]\) 。(这里 \(sum[r] - sum[l-1]\) 表示区间 \([l,r]\) 范围内的石子数量之和)
区间拼接
这里讲的区间拼接其实就是对于两个区间 \([l,j]\) 和 \([j+1,r]\) ,假设他们分别有 \(i-1\) 堆和 \(1\) 堆石子,那么这个区间总共有 \(i\) 堆石子。
区间拼接就不需要考虑合并了。
所以对于区间 \([l,r]\) 包含 \(i\) 堆石子的情况,它对应状态 \(f[l][r][i]\) ,那么它总能拆分成两个状态(\(f[l][j][i-1]\) 和 \(f[j+1][r][1]\) ,其中 \(l \le j \lt r\))的拼接。
可以推导出状态转移方程为: \(f[l][r][i] = min(f[l][j][i-1] + f[j+1][r][1])\) ,其中 \(2 \le i \le len,l \le j \lt r\) 。
实现代码如下:
#include <bits/stdc++.h>
using namespace std;
#define INF (1<<29)
const int maxn = 110;
int n, L, R, a[maxn], sum[maxn], f[maxn][maxn][maxn];
int main() {
while (~scanf("%d%d%d", &n, &L, &R)) {
for (int i = 1; i <= n; i ++) scanf("%d", a+i);
for (int i = 1; i <= n; i ++) sum[i] = sum[i-1] + a[i];
for (int i = 1; i <= n; i ++) for (int j = 1; j <= n; j ++) for (int k = 1; k <= n; k ++) f[i][j][k] = INF;
for (int l = 1; l <= n; l ++) for (int r = l; r <= n; r ++) f[l][r][r-l+1] = 0;
for (int len = 1; len <= n; len ++) {
for (int l = 1; l+len-1 <= n; l ++) {
int r = l+len-1;
for (int i = l; i < r; i ++) {
for (int j = L-1; j < R; j ++) {
f[l][r][1] = min(f[l][r][1], f[l][i][j] + f[i+1][r][1] + sum[r] - sum[l-1]);
}
}
for (int i = 2; i < len; i ++) {
for (int j = l; j < r; j ++) {
f[l][r][i] = min(f[l][r][i], f[l][j][i-1] + f[j+1][r][1]);
}
}
}
}
if (f[1][n][1] == INF) puts("0");
else printf("%d\n", f[1][n][1]);
}
return 0;
}