P1880 石子合并
题目描述
在一个圆形操场的四周摆放N堆石子,现要将石子有次序地合并成一堆.规定每次只能选相邻的2堆合并成新的一堆,并将新的一堆的石子数,记为该次合并的得分。
试设计出1个算法,计算出将N堆石子合并成1堆的最小得分和最大得分.
输入输出格式
输入格式:
数据的第1行试正整数N,1≤N≤100,表示有N堆石子.第2行有N个数,分别表示每堆石子的个数.
输出格式:
输出共2行,第1行为最小得分,第2行为最大得分.
输入输出样例
输入样例#1:
4 4 5 9 4
输出样例#1:
43 54
石子合并问题是经典问题。主要分为3类:1、任意两堆石子合并。2、链状相邻两堆石子合并。3、环状的相邻两堆石子合并。
假设石子合并问题要求合并所得最小值(最大值与最小值一个道理,这里只讲最小值)。
第一类: 每次找出最小的两堆石子进行合并。可以采用优先队列实现。
第二类:dp。 dp[i][j] 是从i到j合并石子,所需要最小值。
转移方程是 dp[i][j] = min(dp[i][j], dp[i][i+k]+dp[i+k+1][j])+sum[i][j]。
sum[i][j]是表示i到j的石子和。 dp[i][i+k]+dp[i+k+1][j]的意思是不断的分成两堆,看哪两堆是最小值。
边界条件是dp[i][i] = 0 因为这堆和本身合并不得分。
第三类:dp。 其实是第二类的变形,只需要 断环为链 就可以。
详细看代码吧。
1 #include <cstdio> 2 3 #define ll long long 4 #define INF 1<<30 5 //开long long,不然f会爆。。。 6 ll aa[210],f1[210][210],f2[210][210],w[210], ans1, ans2; //f1->min f2->max 7 //f1是求最小得分 f2是求最大得分 8 ll Min(ll a, ll b) 9 { 10 return (a < b ? a : b); 11 } 12 ll Max(ll a, ll b) 13 { 14 return (a > b ? a : b); 15 } 16 17 int main() 18 { 19 int n; 20 scanf("%d", &n); 21 for(int i=1; i<=n; i++) 22 { 23 scanf("%lld", &aa[i]); 24 aa[i+n] = aa[i];//断环为链 25 } 26 for(int i=1; i<2*n; i++) 27 w[i] = w[i-1] + aa[i];//处理前缀和 28 //v表示当前合并i到第i+v个石子 最多合并i到 i+n-1个石子 29 for(int v=1; v<n; v++) 30 for(int i=1; i<2*n-v; i++) 31 { 32 f1[i][i+v] = INF; 33 f2[i][i+v] = 0; 34 for(int k=0; k<v; k++) 35 { //这里一定要检查是否拼错 我就因为把f2写成了f1而导致WA 查错好长时间才找出来 36 //注意f1与min对应 f2与max对应。 37 f1[i][i+v] = Min(f1[i][i+v], f1[i][i+k]+f1[i+k+1][i+v]); 38 f2[i][i+v] = Max(f2[i][i+v], f2[i][i+k]+f2[i+k+1][i+v]); 39 }//不要忘记加他们的sum 40 f1[i][i+v] += (w[i+v] - w[i-1]); 41 f2[i][i+v] += (w[i+v] - w[i-1]); 42 } 43 44 45 ans1 = INF; ans2 = 0; 46 //寻找结果 47 for(int i=1; i<=n; i++) 48 { 49 ans1 = Min(ans1, f1[i][i+n-1]); 50 ans2 = Max(ans2, f2[i][i+n-1]); 51 } 52 53 printf("%lld %lld", ans1, ans2); 54 return 0; 55 }