bzoj 3229: [Sdoi2008]石子合并
3229: [Sdoi2008]石子合并
Description
在一个操场上摆放着一排N堆石子。现要将石子有次序地合并成一堆。规定每次只能选相邻的2堆石子合并成新的一堆,并将新的一堆石子数记为该次合并的得分。
试设计一个算法,计算出将N堆石子合并成一堆的最小得分。
Input
第一行是一个数N。
以下N行每行一个数A,表示石子数目。
Output
共一个数,即N堆石子合并成一堆的最小得分。
Sample Input
4
1
1
1
1
1
1
1
1
Sample Output
8
HINT
对于 100% 的数据,1≤N≤40000
对于 100% 的数据,1≤A≤200
题解:
个人认为还是比较简单的(在知道了GarsiaWachs算法后)
我只知道结论,设一个序列是A[0..n-1],每次寻找最小的一个满足A[k-1]<=A[k+1]的k,(方便起见设A[-1]和A[n]等于正无穷大)
那么我们就把A[k]与A[k-1]合并,之后找最大的一个满足A[j]>A[k]+A[k-1]的j,把合并后的值A[k]+A[k-1]插入A[j]的后面。
有定理保证,如此操作后问题的答案不会改变。
举个例子:
186 64 35 32 103
因为35<103,所以最小的k是3,我们先把35和32删除,得到他们的和67,并向前寻找一个第一个超过67的数,把67插入到他后面
186 64(k=3,A[3]与A[2]都被删除了) 103
186 67(遇到了从右向左第一个比67大的数,我们把67插入到他后面) 64 103
186 67 64 103 (有定理保证这个序列的答案加上67就等于原序列的答案)
现在由5个数变为4个数了,继续!
186 (k=2,67和64被删除了)103
186 131(就插入在这里) 103
186 131 103
现在k=2(别忘了,设A[-1]和A[n]等于正无穷大)
234 186
420
最后的答案呢?就是各次合并的重量之和呗。420+234+131+67=852;
#include<stdio.h>
#include<iostream>
#include<algorithm>
#include<string.h>
using namespace std;
const int Max=2000000000;
int n,i,j,k,m,ans,a[40005];
int main()
{
scanf("%d",&n);
for(i=1;i<=n;i++)
scanf("%d",&a[i]);
a[0]=Max;
a[n+1]=Max;
m=n;
for(i=1;i<n;i++)
{
k=0;
for(j=1;j<=m;j++)
if(a[j-1]<=a[j+1])
{
k=j;
break;
}
a[k-1]+=a[k];
for(j=k;j<m;j++)
a[j]=a[j+1];
k--;
ans+=a[k];
while(k>0&&a[k-1]<=a[k])
{
swap(a[k-1],a[k]);
k--;
}
a[m]=Max;
m--;
}
cout<<ans;
return 0;
}
一念起,天涯咫尺; 一念灭,咫尺天涯。