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;
}
posted @ 2019-11-28 19:37  quanjun  阅读(229)  评论(0编辑  收藏  举报