zoukankan      html  css  js  c++  java
  • bzoj3229 [Sdoi2008]石子合并(非dp的GarsiaWachs算法)

    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

    分析:
    我第一眼以为就是一个简单的dp
    还在怀疑十年前的sdoi竟然这么简单
    然而,当我看到了n<=40000
    朴素的n^3肯定是不行了
    看这个数据范围,起码要nlogn

    此题如果数据范围是100,就是一道基础的动态规划题
    但是数据范围到了40000,就是一道比较难的题了

    这里引入GarsiaWachs算法

    主要过程是这样的:
    从左往右找,找到第一个k,使得a[k-1]<=a[k+1],我们把这两堆石子合并,把代价加在sum上
    然后从k往左找,找到第一个j,使得a[j]>合并后的石头,把合并后的石头放在a[j]后
    合并n-1次后,sum就是合并n堆石子的最小代价

    之下是网上不错的讲解:
    GarsiaWachs算法可以把时间复杂度压缩到O(nlogn)。
    具体的算法及证明可以参见《The Art of Computer Programming》第3卷6.2.2节Algorithm G和Lemma W,Lemma X,Lemma Y,Lemma Z

    算法概要:

    设一个序列是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

    证明嘛,基本思想是通过树的最优性得到一个节点间深度的约束,之后
    证明操作一次之后的解可以和原来的解一一对应,并保证节点移动之后他所在的
    深度不会改变。详见TAOCP。

    解题思路:

    (这是从网上扒下来的一个关于GarsiaWachs算法的解释)

    1. 这类题目一开始想到是DP, 设dp[i][j]表示第i堆石子到第j堆石子合并最小得分.
      状态方程: dp[i][j] = min(dp[i][k]+dp[k+1][j]+sum[j]-sum[i-1]);
      sum[i]表示第1到第i堆石子总和. 递归记忆化搜索即可.

    2. 不过此题有些不一样, 1<=n<=40000范围特大

    问题分析:

    (1). 假设我们只对3堆石子a,b,c进行比较, 先合并哪2堆, 使得得分最小.
    score1 = (a+b) + ( (a+b)+c )
    score2 = (b+c) + ( (b+c)+a )
    再次加上score1 <= score2, 化简得: a <= c, 可以得出只要a和c的关系确定,
    合并的顺序也确定.

    (2). GarsiaWachs算法, 就是基于(1)的结论实现.找出序列中满足stone[i-1] <=
    stone[i+1]最小的i, 合并temp = stone[i]+stone[i-1], 接着往前面找是否
    有满足stone[j] > temp, 把temp值插入stone[j]的后面(数组的右边). 循环
    这个过程一直到只剩下一堆石子结束.

    (3). 为什么要将temp插入stone[j]的后面, 可以理解为(1)的情况
    从stone[j+1]到stone[i-2]看成一个整体 stone[mid],
    现在stone[j],stone[mid], temp(stone[i-1]+stone[i-1]),
    因为temp < stone[j],
    因此不管怎样都是stone[mid]和temp先合并, 所以讲temp值插入stone[j]的后面是不影响结果.

    tip

    我在这里用的模拟链表,链接的时候一定要注意

    这里写代码片
    #include<cstdio>
    #include<cstring>
    #include<iostream>
    
    using namespace std;
    
    const int INF=0x33333333;
    const int N=50000;
    int t[N][2],a[N],ans=0;
    int n;
    
    void doit()
    {
        int i,j,k;
        for (i=1;i<n;i++)
        {
            j=t[0][1];
            int fro=t[j][0],beh=t[j][1];
            while (a[fro]>a[beh])
            {
                j=beh;
                fro=t[j][0];
                beh=t[j][1];
            }
            int tt=a[j]+a[fro];
            ans+=tt;
            a[j]=tt;
            t[t[fro][0]][1]=t[j][1]; t[t[j][1]][0]=t[fro][0];  //del
            k=t[fro][0];
            while (a[k]<=tt)
                k=t[k][0];
            t[j][1]=t[k][1]; t[t[k][1]][0]=j;  //insert
            t[j][0]=k; t[k][1]=j;
        }
        printf("%d",ans);
    }
    
    int main()
    {
        scanf("%d",&n);
        for (int i=1;i<=n;i++) scanf("%d",&a[i]),t[i][0]=i-1,t[i][1]=i+1;
        a[0]=INF; a[n+1]=INF;
        t[0][1]=1; t[n+1][0]=n;
        doit();
        return 0;
    }
  • 相关阅读:
    jmeter压力测试报错:java.net.BindException: Address already in use: connect
    C# 对话框总结(转载)
    C# 文件操作方法大全(转载)
    C#实现进度条progress control(转载)
    在Windows下架设FTP服务器
    .Net环境下,使用installutil.exe注册、删除windows服务
    用双网卡实现跨网段访问(转载)
    HTTP协议详解(转载)
    学习开发web服务(转载)
    C# 实现http协议的GET和POST请求(转载)
  • 原文地址:https://www.cnblogs.com/wutongtong3117/p/7673217.html
Copyright © 2011-2022 走看看