石子合并——圆形版
一个较为常规的dp,用上了环的处理。
问题描述
Submit Status
虽然方老师赚到了很多钱,但是有些钱却非常难处理,经常会有人会以一大袋金币的方式来支付方老师的演讲报酬。现在方老师家里已经放置了N袋金币了,每一袋金币质量为Ai,他想把这些金币合并在一袋里面,然后存到银行去,于是方老师把这N袋金币围成一个圈,每一次他可以把相邻两袋金币合并,并消耗两袋金币质量总和的体力,他想让你帮他算下,他最少消耗多少体力可以完成这项工作。
注意:开始时第i袋金币与第i+1袋金币相邻,第1袋金币与第N袋金币相邻。
dp思路
将问题简化到直线上,观察每次合并的过程,发现对于区间[i, j],无论中间过程是怎样的,最后一次合并所花费的体力都是一定的,大小为区间[i, j]内的金币的总重量,且对于区间[i, j],当分成的两部分的体力花费最小时,总花费最小。
故易得出状态转移方程
d[i][j] = min(d[i][k]+d[k+1][j], d[i][j]) + sum[j] - sum[i]; (i<=k<j)
然后,考虑环,根据以往处理环的经验,(i+k)%n可得出i后第k个数字,但是此处的j不方便用这个方法表示,故将d[i][j]的定义改为第i个数字及后j个数字的最小解。此时可得新的转移方程
d[i][j] = min(d[i][k] + d[(i+k)%n+1][j-k-1], d[i][j]) + sum[i][j]; (0<=k<j)
代码
#include<cstdio>
#include<algorithm>
#include<iostream>
#include<cstring>
#include<cstdlib>
using namespace std;
const int INF = 2139062143;
int n;
int sum[1005][1005], a[1005];
int d[1005][1005];
void read_in() {
cin >> n;
a[0] = 0;
for(int i = 1; i <= n; i++){
int x;
scanf("%d", &x);
a[i] = a[i-1] + x;
}
for(int i = 1; i <= n; i++)
for(int j = 1; j < n; j++)
if((i+j) > n) sum[i][j] = a[n] - a[i-1] + a[(i+j)%n];
else sum[i][j] = a[i+j] - a[i-1];
return;
}
int dp(int i, int j){
if(d[i][j] < INF) return d[i][j];
int m = INF;
for(int k = 0; k < j; k++)
m = min(dp(i, k) + dp((i+k)%n+1,j-k-1), m);
return d[i][j] = m + sum[i][j];
}
int main() {
read_in();
memset(d, 127, sizeof(d));
for(int i = 1; i <= n; i++)
d[i][0] = 0;
int min_ans = INF;
for(int i = 1; i <= n; i++) min_ans = min(min_ans, dp(i, n-1));
cout << min_ans;
return 0;
}