【BZOJ 3229】 [Sdoi2008]石子合并

3229: [Sdoi2008]石子合并

Time Limit: 3 Sec Memory Limit: 128 MB
Submit: 312 Solved: 148
[Submit][Status][Discuss]
Description

  在一个操场上摆放着一排N堆石子。现要将石子有次序地合并成一堆。规定每次只能选相邻的2堆石子合并成新的一堆,并将新的一堆石子数记为该次合并的得分。
  试设计一个算法,计算出将N堆石子合并成一堆的最小得分。

Input

  第一行是一个数N。
  以下N行每行一个数A,表示石子数目。

Output

  共一个数,即N堆石子合并成一堆的最小得分。

Sample Input

4

1

1

1

1

Sample Output

8

HINT

对于 100% 的数据,1≤N≤40000

对于 100% 的数据,1≤A≤200

GarsiaWachs算法。

直接dp可以用四边形不等式优化到 O(n2) ,用GarsiaWachs是 O(nlogn) 的。

简述一下GarsiaWachs算法的流程:
【假设 a[0]=a[n+1]=inf
1.从序列的左端开始找第一个 a[k1]a[k+1] k ,然后合并 a[k1],a[k]

2.从当前位置开始向左找第一个 a[j]>a[k1]+a[k] j ,把合并后的值插到 j 的后面

3.一直这样重复下去直到剩下一堆

具体算法证明:《The Art of Computer Programming》第3卷6.2.2节Algorithm G和Lemma W,Lemma X,Lemma Y,Lemma Z。
(还没有看。。)

#include <iostream>
#include <algorithm>
#include <cmath>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#define LL long long
using namespace std;
int t,a[50005],n;
LL ans=0;
void Combine(int k)
{
    int tmp=a[k-1]+a[k];
    ans+=tmp;
    for (int i=k;i<t-1;i++)
        a[i]=a[i+1];
    t--;
    int j=0;
    for (j=k-1;j>0&&a[j-1]<tmp;j--)
        a[j]=a[j-1];
    a[j]=tmp;
    while (j>=2&&a[j]>=a[j-2])
    {
        int d=t-j;
        Combine(j-1);
        j=t-d;
    }
}
int main()
{
    scanf("%d",&n);
    ans=0;
    for (int i=0;i<n;i++)
        scanf("%d",&a[i]);
    t=1;
    for (int i=1;i<n;i++)
    {
        a[t++]=a[i];
        while (t>=3&&a[t-3]<=a[t-1])
            Combine(t-2);
    }
    while (t>1)
        Combine(t-1);
    cout<<ans<<endl;
    return 0;
}

这里写图片描述

感觉这道题代码实现很巧妙,证明是通过转化到树上blablabla..

你可能感兴趣的:(OI,bzoj,GarsiaWach)