题目
题目描述
在一个圆环上分布着N堆石子,现要将石子有次序地合并成一堆.规定每次只能选相邻的2堆合并成新的一堆,并将新的一堆的石子数记为得分。
输入描述
每组数据的第1行为正整数N,1≤N≤100,表示有N堆石子.第2行有N个数,分别表示每堆石子的个数.
输出描述
输出共2行,第1行为最小得分,第2行为最大得分
样例输入
4
4 4 5 9
样例输出
43
54
题目分析
此题和我的上一篇题解十分相近,只需要做出一点微小的改动即可。
因为是环状,所以可以首尾合并了。那么为了简单起见,用空间换写起来方便【大雾】,直接将数据往后扩展了一倍,这样就和上一题一模一样了
唯一需要注意的是:答案不再存储在dp[1][n],因为考虑到首尾合并的问题,实际上是向后滑动了的。因此,最小代价/得分为 min{dp[1][n],dp[2][n+1],……dp[n][2n-1]}。最大代价/得分同理求得。
本题的区间DP
用dp[i][j]表示合并[i,j]区间所需要的最小代价
那么可以方便的得到如下关系
若i=j,则dp[i][j]=0
否则,则dp[i][j]=min{dp[i][k]+dp[k+1][j]}+sum[i][j]
发现:计算dp[i][j]的大区间,需要先计算出dp[i][j]的小区间,所以可以考虑设定一个变量len,从小往大递增,根据此来控制先算小区间,再算大区间
最后,本题要求的是合并所有石子,即[i,j]区间和并的最小代价。
因此,本题答案储存在dp[1][n]内
整体代码与运行结果
/*
ZhangBinjie@Penguin
*/
#include<iostream>
#include<algorithm>
#include<cstring>
#include<queue>
#define maxn 105
using namespace std;
int a[maxn*2];
int dp[maxn*2][maxn*2];
int dpm[maxn * 2][maxn * 2];
int sum[maxn*2][maxn*2];
int main() {
int n;
int ma, mi;
while (cin >> n) {
memset(dp, 0, sizeof(dp));
memset(dpm, 0, sizeof(dpm));
for (int i = 1; i <= n; ++i) {
cin >> a[i];
a[n + i] = a[i];
}
for (int i = 1; i <= n; ++i) {
sum[i][i] = a[i];
sum[n + i][n + i] = a[i];
for (int j = i+1; j <= 2*n; ++j) {
sum[i][j] = sum[i][j - 1] + a[j];
sum[n + i][n + j] = sum[n + i][n + j - 1] + a[j];
}
}
for (int len = 2; len <= n; ++len) {
for (int i = 1; i <= 2 * n - len + 1; ++i) {
dp[i][i + len - 1] = dp[i][i] + dp[i + 1][i + len - 1] + sum[i][i + len - 1];
dpm[i][i + len - 1] = dpm[i][i] + dpm[i + 1][i + len - 1] + sum[i][i + len - 1];
for (int k = i + 1; k < i + len - 1; ++k) {
dp[i][i + len - 1] = min(dp[i][i + len - 1], dp[i][k] + dp[k + 1][i + len - 1] + sum[i][i + len - 1]);
dpm[i][i + len - 1] = max(dpm[i][i + len - 1], dpm[i][k] + dpm[k + 1][i + len - 1] + sum[i][i + len - 1]);
}
}
}
mi = dp[1][n];
ma = dpm[1][n];
for (int i = 2; i < n; ++i) {
mi = min(mi, dp[i][i + n - 1]);
ma = max(ma, dpm[i][i + n - 1]);
}
cout << ma << " " << mi << endl;
}
return 0;
}
结果如下