石子合并(一)
时间限制:1000 ms | 内存限制:65535 KB
难度:3
-
描述
- 有N堆石子排成一排,每堆石子有一定的数量。现要将N堆石子并成为一堆。合并的过程只能每次将相邻的两堆石子堆成一堆,每次合并花费的代价为这两堆石子的和,经过N-1次合并后成为一堆。求出总的代价最小值。
-
输入
- 有多组测试数据,输入到文件结束。
每组测试数据第一行有一个整数n,表示有n堆石子。
接下来的一行有n(0< n <200)个数,分别表示这n堆石子的数目,用空格隔开
输出 - 输出总代价的最小值,占单独的一行 样例输入
-
3 1 2 3 7 13 7 8 16 21 4 18
样例输出 -
9 239
-
经典的动态规划问题,首先想到用单一的d(i)作为状态:前i堆石子的最小代价,但是发现前i堆得最优并不一定是全局最优,所以要找出每个状态的所有情况选择最优。定义状态转移方程为d(i,j) = min(d(i, k) + d(k + 1, j) + sum(i, j), i <= k < j);
-
重点是发现总的最优等于局部最优和加上这部分所有石子的和
-
-
递归版:
-
#include <stdio.h> #include <string.h> int dist[202][202]; int p[202]; int sum[202]; int dp(int i, int j){ int k, q; int &ans = dist[i][j]; if(ans > -1) return ans; else if(i == j) ans = 0; else if(i + 1 == j) { ans = p[i] + p[j]; } else { ans = 2100000000; for(k = i; k < j; ++k) { q = dp(i, k) + dp(k + 1, j) + sum[j] - sum[i] + p[i]; if(ans > q) ans = q; } } return ans; } int main(void){ int n, i; while(scanf("%d", &n) != EOF) { memset(dist, -1, sizeof(dist)); for(i = 0; i < n; ++i) { scanf("%d", &p[i]); sum[i] = 0; if(i == 0) sum[i] =p[i]; else { sum[i] += sum[i - 1] + p[i]; } } printf("%d\n", dp(0, n - 1)); } return 0; }
#include <stdio.h> #include <string.h> int dist[202][202]; int p[202]; int sum[202]; int main(void){ int n, i, j, k, l, q; while(scanf("%d", &n) != EOF) { memset(dist, -1, sizeof(dist)); for(i = 0; i < n; ++i) { scanf("%d", &p[i]); sum[i] = 0; if(i == 0) sum[i] =p[i]; else { sum[i] += sum[i - 1] + p[i]; } } for(i = 0; i < n; ++i) { dist[i][i] = 0; } for(i = 0; i < n; ++i) { dist[i][i + 1] = p[i] + p[i + 1]; } for(l = 2; l <= n; ++l) { for(i = 0; i <= n - l + 1; ++i) { j = i + l - 1; dist[i][j] = 2100000000; for(k = i; k < j; ++k) { q = dist[i][k] + dist[k + 1][j] + sum[j] - sum[i] + p[i]; if(dist[i][j] > q) dist[i][j] = q; } } } printf("%d\n", dist[0][n - 1]); } return 0; }
- 有多组测试数据,输入到文件结束。