P5569 [SDOI2008] 石子合并

Posted on 2022-10-31 21:38  Capterlliar  阅读(102)  评论(0编辑  收藏  举报

题意:和普通的石子合并区别在于n的范围,n≤50000。

解:GarsiaWachs算法。

算法流程不复杂:从左到右找出第一个满足ai-1<ai+1的位置,将ai-1和ai合并为temp,放到它们左边第一个大于等于temp的值的右边。这里的大于和小于都可以换成大于等于,小于等于。

下面口胡一下算法原理。真正的证明在这里:A New Proof of the Garsia-Wachs Algorithm

考虑只有三个石子的情况,令三个石子价值分别为a、b、c,那么答案等于min((a+b)+(a+b+c), (b+c)+(a+b+c)),也就是最终答案取决于a和c,如果a<c,那就先合并a和b;

考虑不限制只有相邻的两个石子才能合并。那就是构造一棵哈夫曼树,每次选两个最小的合并在一起。

现在把上面两点结合起来,可以理解为 n个有序键构造二叉查找树的问题(By OI Wiki)。首先对于序列中的一个三元组(ai-1, ai ,ai+1),满足ai-1<ai+1,将ai-1和ai合并,即放在树中同一高度,一定比将ai和ai+1放在一起更优。

然后证明将合并后的数放到前面是合法的。

首先证明换完之后答案不会更差。对于以ai开始往左第一个大于temp的值,设其为ak,原来排在它后面的为ak+1在树中深度为hk+1,hk+1要么等于h- 1,要么等于hi。大概是前面这一段既没有满足ai-1<ai+1的三元组,不可能在ai-1之前被合并;也不大于ai-1+ai,不会在temp之后被合并。原文用了反证法,意为hk+1<hi-1时总有一种重构能使答案更优。在此基础上可以说明这样交换答案不会更差,因为它能换回去。

接着证明交换后依然可以通过重构这棵树获得同样的答案,来满足只能合并相邻两个数的条件。具体操作没看懂,大概还是进行一些位移。证到这里显然就证完了。代码用vector很好写,但要开O2优化。

代码:

#include <bits/stdc++.h>
using namespace std;
#define maxx 705
#define maxn 25
#define maxm 205
#define ll long long
#define inf 1000000009
#define mod 1000000007
vector<int> v;
int change(){
    int len=v.size();
    int k=v.size()-2;
    for(int i=0;i<=len-3;i++){
        if(v[i]<=v[i+2]){
            k=i;
            break;
        }
    }
    int temp=v[k]+v[k+1];
    int j;
    for(j=k;j>=0;j--){
        if(v[j]>=temp)
            break;
    }
    v.erase(v.begin()+k);
    v.erase(v.begin()+k);
    v.insert(v.begin()+j+1,temp);
    return temp;
}
//a b c
//(a+b)+(a+b+c)
//(b+c)+(a+b+c)
signed main() {
//    int T;
//    scanf("%d",&T);
//    while(T--) {;
//
//    }
    int n;
    scanf("%d",&n);
    for(int i=1;i<=n;i++){
        int t;
        scanf("%d",&t);
        v.push_back(t);
    }
    int ans=0;
    for(int i=1;i<n;i++){
        ans+=change();
    }
    printf("%d",ans);
    return 0;
}
View Code