zoukankan      html  css  js  c++  java
  • 石子合并问题 & GarsiaWachs算法

    引入

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

    洛谷例题:
    数据较小可以用区间DP过:P1880 [NOI1995] 石子合并
    数据较强可以用GarsiaWachs算法过:P5569 [SDOI2008]石子合并

    一个较为朴素的算法

    不是暴力

    按照区间DP的思路来做

    \(f_{i, j}\) 表示区间 \([i, j]\) 合并的最小得分,枚举一个端点 \(k\),那么有转移方程:(其中 \(a\) 数组是预处理后的前缀和)

    \[f_{i, j} = \min \{ f_{i, j}, f_{i, k} + f_{k + 1, j} + a_j - a_{i - 1} \} \]

    注意 \(i\) 要倒序枚举,\(j\) 要正序枚举

    答案就是 \(f_{1, n}\)

    时间复杂度: \(O(n^3)\);空间复杂度:\(O(n^2)\)

    Code

    /*
    Work by: Suzt_ilymics
    Knowledge: ??
    Time: O(??)
    */
    #include<iostream>
    #include<cstdio>
    #include<cstring>
    #include<algorithm>
    #define LL long long
    #define orz cout<<"lkp AK IOI!"<<endl
    
    using namespace std;
    const int MAXN = 22335;
    const int INF = 1e9+7;
    const int mod = 1e9+7;
    
    int n;
    int a[MAXN];
    int f[MAXN][MAXN];
    
    int read(){
        int s = 0, f = 0;
        char ch = getchar();
        while(!isdigit(ch))  f |= (ch == '-'), ch = getchar();
        while(isdigit(ch)) s = (s << 1) + (s << 3) + ch - '0' , ch = getchar();
        return f ? -s : s;
    }
    
    int main()
    {
        n = read();
        for(int i = 1; i <= n; ++i) a[i] = read() + a[i - 1];
        for(int i = n; i >= 1; --i){
    	for(int j = i; j <= n; ++j){
                if(i == j) continue;
    	    f[i][j] = INF;
    	    for(int k = i; k < j; ++k) f[i][j] = min(f[i][j], f[i][k] + f[k + 1][j] + a[j] - a[i - 1]);
    	}
        }
        printf("%d\n", f[1][n]);
        return 0;
    }
    

    GarsiaWachs算法

    发现数据范围太大了,上面的算法已经不能满足我们的需求,这里有一种优化算法,专门用来解决石子问题

    每次操作,从前向后找一个最小的 \(k\),使其满足 \(a_{k - 1} \le a_{k + 1}\),然后合并 \(a_{k - 1}\)\(a_k\)
    \(k\) 开始向前找到第一个 \(j\) 使得 \(a_j > a_{k - 1} + a_k\),并将合并后的新值插入位置 \(j\) 后面
    进行 \(n - 1\) 次结束,在合并过程中统计答案即可

    时间复杂度:\(O(n^2)\);空间复杂度:\(O(n)\)

    正确性证明:作为一个OIer,会应用就好啦,其实是我不会

    那如果求最大分数呢?
    把权值取个相反数不就得了,输出答案的时候再取回来

    那如果把石子围成一圈呢?
    嗯……我没想到有什么方式能够转化,但经过实验,断环成链的方法是不行的,(除非你把所有的断环成链情况都跑一边,但那样复杂度就上去了)

    Code

    /*
    Work by: Suzt_ilymics
    Knowledge: ??
    Time: O(??)
    */
    #include<iostream>
    #include<cstdio>
    #include<cstring>
    #include<algorithm>
    #include<vector>
    #define LL long long
    #define orz cout<<"lkp AK IOI!"<<endl
    
    using namespace std;
    const int MAXN = 1e5+5;
    const int INF = 1e9+7;
    const int mod = 1e9+7;
    
    LL n, ans = 0;
    vector<int> a;
    
    int read(){
        int s = 0, f = 0;
        char ch = getchar();
        while(!isdigit(ch))  f |= (ch == '-'), ch = getchar();
        while(isdigit(ch)) s = (s << 1) + (s << 3) + ch - '0' , ch = getchar();
        return f ? -s : s;
    }
    
    int merge(){//GarsiaWachs算法
        int k = a.size() - 2;
        for(int i = 0; i < a.size() - 2; ++i)
        	if(a[i] <= a[i + 2]){ k = i; break;}
        int sum = a[k] + a[k + 1];
        a.erase(a.begin() + k);
        a.erase(a.begin() + k);
        int inst = -1;
        for(int i = k - 1; i >= 0; --i)
        	if(a[i] > sum){ inst = i; break; }
        a.insert(a.begin() + inst + 1, sum);
        return sum;
    }
    
    int main()
    {
        n = read();
        for(int i = 1; i <= n; ++i) a.push_back(read());
        for(int i = 1; i < n; ++i) ans += merge();
        printf("%lld", ans);
        return 0;
    }
    

    放两张关于GarsiaWachs算法的图(反正我没看懂,扔给你们了

  • 相关阅读:
    golang实现dns域名解析(一)
    互联网协议入门(一)(转)
    DNS入门(转)
    随笔:Golang 时间Time
    mysql查询某一个字段是否包含中文字符
    screen状态变Attached连接会话失败
    golang :连接数据库闲置断线的问题
    神奇的GO语言:空接口(interface)
    Go语言:变参函数
    go语言:函数参数传递详解
  • 原文地址:https://www.cnblogs.com/Silymtics/p/14407370.html
Copyright © 2011-2022 走看看