石子合并问题

问题:

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

O(n3)

f[i][j]=min{f[i][k]+f[k+1][j]}(ikj)+w(i,j)

这应该是普及组的DP。

(对于环形只需要复制一遍来个 2n 的DP就好了)
环形DP(Luogu1880):http://paste.ubuntu.com/25619945/

O(n2)

首先有四边形不等式。
四边形不等式需要满足的条件:
1. f[i][j]f[i][j](iijj)
即区间包含的单调性。

2. f[i][j]=min{f[i][k1]+f[k,j]}+w(i,j)(ikj)
w(i,j)+w(i,j)w(i,j)+w(i,j)(ii<jj)

如果满足四边形不等式,那么 f 满足决策单调。

显然合并石子的dp函数满足以上任意两个条件。所以决策单调性优化,复杂度变成了 O(n2)
(只能做最小值)
Code:http://paste.ubuntu.com/25620177/

当然,利用贪心思想,可以得出最大值的 O(n2) 转移:


for (int i=n*2-1;i>=1;--i)
    for (int j=i+1;j<=min(n*2-1,i+n-1);++j)
        f[i][j]=max(f[i][i]+f[i+1][j],f[j][j]+f[i][j-1])+w[j]-w[i-1];

O(nlogn)

经过上面的贪心,应该可以发现:
如果先合并一个值两边的较大值,结果不会最优。(这不是严格证明,严格证明请看:《The Art of Computer Programming》第3卷6.2.2节Algorithm G和Lemma W,Lemma X,Lemma Y,Lemma Z)。

GarsiaWachs算法
http://www.cnblogs.com/lwq12138/p/5425465.html

个人认为还是比较简单的(在知道了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;

BZOJ3229:

#include<bits/stdc++.h>
using namespace std;
struct IO{
    streambuf *ib,*ob;
    inline void init(){
        ios::sync_with_stdio(false);
        cin.tie(NULL);cout.tie(NULL);
        ib=cin.rdbuf();ob=cout.rdbuf();
    }
    inline int read(){
        static char ch;static int i,f;
        ch=ib->sbumpc();i=0,f=1;
        while(!isdigit(ch)){
            if(ch==-1)return false;
            if(ch=='-')f=-1;
            ch=ib->sbumpc();
        }
        while(isdigit(ch)){
            i=(i+(i<<2)<<1)+ch-'0';
            ch=ib->sbumpc();
        }
        return ((f>0)?i:-i);
    }
    inline void W(long long x){
        static int buf[50];
        if(!x){ob->sputc('0');return;}
        if(x<0){ob->sputc('-');x=-x;}
        while(x){buf[++buf[0]]=x%10;x/=10;}
        while(buf[0]){ob->sputc(buf[buf[0]--]+'0');}
    }
}io;

const int Maxn=5e4+5;
int n,m,a[Maxn];
const int INF=0x3f3f3f3f;
long long ans;
int main()
{
    io.init();n=io.read();m=n+1;
    a[0]=a[n+1]=INF;
    for(int i=1;i<=n;i++)a[i]=io.read();
    for(int i=1;i<n;i++)
    {
        int pos=0;
        for(int j=1;j<m;j++)if(a[j+1]>=a[j-1]){pos=j;break;}
        a[pos-1]+=a[pos];ans+=a[pos-1];
        for(int j=pos;j<m;j++)a[j]=a[j+1];
        m--;
        for(int j=pos-1;j>=1;j--)
        {
            if(a[j-1]>a[j])break;
            swap(a[j-1],a[j]);
        }
    }
    io.W(ans);
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值