题目描述
链接:https://ac.nowcoder.com/acm/problem/50493
来源:牛客网
将n堆石子绕圆形操场排放,现要将石子有序地合并成一堆。规定每次只能选相邻的两堆合并成新的一堆,并将新的一堆的石子数记做该次合并的得分。
请编写一个程序,读入堆数n及每堆的石子数,并进行如下计算:
1、选择一种合并石子的方案,使得做n-1次合并得分总和最大。
2、选择一种合并石子的方案,使得做n-1次合并得分总和最小。
题目思路
原问题:n堆石子合并n-1次后得分总和的最大值/最小值
->从第i堆石子开始向后合并n堆石子得分总和的最大值/最小值
子问题:从第i堆石子开始向后合并j堆石子得分总和的最大值/最小值。
石子环形摆放,在求得分时就要考虑到取模运算。
设dp1[i][j], 从第i堆石子开始向后合并j堆的得分总和最小值;
dp2[i][j], 从第i堆石子开始向后合并j堆的得分总和最大值;
sum[i][j], 从第i堆石子开始向后数j堆的石子数之和。
状态转移方程 dp1[i][j] = min(dp1[i][j], dp1[i][k]+dp1[(i+k-1)%n + 1][j-k]),
dp2[i][j] = max(dp2[i][j], dp2[i][k]+dp2[(i+k-1)%n + 1][j-k])
Q1.怎样理解(i+k-1)%n + 1?
(i+k-1)%n 计算上一部分石子最后一堆的位置。取模运算是为了,若上一堆石子已经将圆圈的头尾包含进去,就需要找到当前堆石子的正确序号。不直接(i+k)%n 是为了防止出现dp[0][j-k]的情况。
Q2.对dp1、dp2数组的遍历顺序是怎样的?
根据状态转移方程,可以发现dp数组填充时,dp[i][j]需要用到在其左下方位的数字。因此不能采用从上到下、从左向右的遍历方式。
本题中采用先列后行的遍历方式,就解决了问题。
完整代码
#include<bits/stdc++.h> using namespace std; const int MAXN = 210; long long dp1[MAXN][MAXN], dp2[MAXN][MAXN], sum[MAXN][MAXN]; int a[MAXN]; int main() { int n; scanf("%d",&n); for(int i=1;i<=n;i++) { scanf("%d", &a[i]); ///a[i+n] = a[i]; } for(int i = 1; i<=n; i++) { for(int j=1; j<=n; j++) { for(int k=i; k<i+j; k++) { int k1 = k%n; if(k1==0) k1 = n; sum[i][j] += a[k1]; } } } long long minx = 1000000, maxx = 0; for(int j=2;j<=n;j++) { for(int i=1;i<=n;i++) { dp1[i][j] = 1000000;//注意这里预设最小值的操作,用memset能预设的值最大为255了 dp2[i][j] = 0; for(int k=1;k<j;k++)//注意条件是k<j,不是<=, <=会对max造成影响 { dp1[i][j] = min(dp1[i][j], (dp1[i][k] + dp1[(i+k-1)%n+1][j-k] + sum[i][j])); dp2[i][j] = max(dp2[i][j], (dp2[i][k] + dp2[(i+k-1)%n+1][j-k] + sum[i][j])); } } } for(int i=1;i<=n;i++) { if(dp1[i][n] < minx) minx = dp1[i][n]; if(dp2[i][n] > maxx) maxx = dp2[i][n]; } printf("%d\n%d\n",minx,maxx); return 0; }