在一个圆形操场的四周摆放着n 堆石子。现要将石子有次序地合并成一堆。规定每次只能选相邻的2 堆石子合并成新的一堆,并将新的一堆石子数记为该次合并的得分。试设计一个算法,计算出将n堆石子合并成一堆的最小得分和最大得分。
编程任务:
对于给定n堆石子,编程计算合并成一堆的最小得分和最大得分。
Input
输入包括多组测试数据,每组测试数据包括两行。
第1 行是正整数n,1<=n<=100,表示有n堆石子。
第2行有n个数,分别表示每堆石子的个数。
Output
对于每组输入数据,输出两行。
第1 行中的数是最小得分;第2 行中的数是最大得分。
Sample Input
4 4 4 5 9
Sample Output
43 54
思路:
1、因为石子围成了一圈,所以先要将n堆的石子复制到第n+1~第2*n堆,这样才能保证无论从哪一堆开始合并,都能取到任意长度的石子堆。
2、dp[i][j] 表示从第i堆开始合并长度为j堆的石子的最优情况,这样可以求得从任意堆开始长度为x的石子堆的最优情况;
3、sum[i]表示从i开始的长度为x(变长)的石子堆所有石子总数。
4、dp[i][j] = max { dp[i][k] , dp[i+k][j-k] } + sum[i]; ( 1<=k<=n )
但是,必须注意此处的dp[i+k][j-k],若循环只到n,可能会越界即可能取到没有算过的值,例如当n=5时,dp[5][4] = max(或min){ dp[5][1] , dp[6][3] } + sum[5]; 此处的dp[6][3]就没有被赋过值,导致“越界”。由于此处的dp[6][3]实际上相当于第1、2、3堆石子合并的最优解,即dp[1][3]。所以当(i+k)%n!=0时,dp[(i+k)%n][j+k] = dp[i+k][j-k];
所以最后: dp[i][j] = max(或min) { dp[i][k] , dp[(i+k)%n][j-k] } + sum[i];
5、还有个要注意的长度为1的石子合并的得分一定为0。
代码:
#include<iostream>
#include<string.h>
#include<stdio.h>
#define INF 0x3ffffff
using namespace std;
int main()
{
int a[220];
int dp1[110][110],dp2[110][110];
int sum[110];
int n,ans1,ans2;
int i,j,k;
while(scanf("%d",&n)!=EOF)
{
for(i=1;i<=n;i++) scanf("%d",&a[i]);
// if(n==1) { printf("0\n0\n"); continue; }
for(int i=1;i<=n;i++) a[n+i]=a[i];
for(i=1;i<=n;i++)
{
sum[i]=a[i]+a[i+1];
for(j=1;j<=n;j++)
{
if(j==2) dp1[i][j]=dp2[i][j]=a[i]+a[i+1];
else dp1[i][j]=INF, dp2[i][j]=-INF;
}
dp1[i][1]=dp2[i][1]=0;
}
ans1=INF; ans2=-INF;
for(j=3;j<=n;j++)
{
for(i=1;i<=n;i++)
{
sum[i]+=a[i+j-1];
int t1,t2;
for(k=1;k<j;k++)
{
if((i+k)%n!=0)
t1=dp1[i][k]+dp1[(i+k)%n][j-k]+sum[i],
t2=dp2[i][k]+dp2[(i+k)%n][j-k]+sum[i];
else
t1=dp1[i][k]+dp1[i+k][j-k]+sum[i],
t2=dp2[i][k]+dp2[i+k][j-k]+sum[i];
dp1[i][j]=min(t1,dp1[i][j]);
dp2[i][j]=max(t2,dp2[i][j]);
}
}
}
for(i=1;i<=n;i++)
{
ans1=min(dp1[i][n],ans1);
ans2=max(dp2[i][n],ans2);
}
printf("%d\n%d\n",ans1,ans2);
}
return 0;
}