zoukankan      html  css  js  c++  java
  • AcWing 1068. 环形石子合并

    题目传送门

    一、前序知识

    \(AcWing\) \(282\). 石子合并

    二、环形样例理解

    三、问题如何转化

    我们是不是可以利用以前学习过的\(AcWing\) \(282\). 石子合并那道题进行扩展来解决呢?因为共\(n\)个节点,那么就是需要合并\(n-1\)次,在图上理解就是

    这样,我们枚举\(n-1\)次,即可以把这个环形问题转化为一个经典的区间\(dp\)问题,接下来我们计算一下时间:

    原来\(282\)的时间复杂度是\(O(N^3)\),这里面还需要执行\(n-1\)次,那就时间复杂度就是\((n-1)O(N^3)=O(N^4)\),现在\(N\)的上限是\(200\),\(200^4=1,600,000,000\),很显然会超时,此路不通。

    四、优化办法

    环形问题的经典优化办法
    1、构造一个长度为\(2*n\)的区间,模拟尾首相连。

    启发我们在长度是\(2n\)的链上进行一次石子合并问题,预处理出来所有的区间\(f[i][j]\),然后枚举区间长度为\(n\)的所有子区间,就一定能枚举到\(n\)种情况了。\(f[i][j]->f[i][j+n-1]\)

    这样的时间复杂度就是\(O((2N)^3)\),然后再通过一次常数的\(O(N)\)就可以搞定了,看清楚,这两个时间复杂度是加法关系,如此,我们就把一个时间复杂度是\(O(N^4)\)的算法,优化为一个\(O((2N)^3)\)级别的算法,\(400^3=64,000,000\)
    是可以一秒通过的。

    而且这个办法是一个通用办法,是可以把环形问题转化。

    五、实现代码

    #include <bits/stdc++.h>
    
    using namespace std;
    //准备两倍的空间
    const int N = 410;
    const int INF = 0x3f3f3f3f;
    int n;      //n个节点
    int s[N];   //前缀和
    int a[N];   //记录每个节点的石子数量
    int f[N][N];//区间DP的数组(最大值)
    int g[N][N];//区间DP的数组(最小值)
    
    //环形DP:学习通用办法
    int main() {
        cin >> n;
        for (int i = 1; i <= n; i++) {
            cin >> a[i];
            //复制后半段的数组,构建一个长度为2*n的数组,环形DP问题的处理技巧
            a[i + n] = a[i];
        }
        //预处理前缀和
        for (int i = 1; i <= n * 2; i++)s[i] = s[i - 1] + a[i];
    
        //预求最小,先放最大
        memset(f, 0x3f, sizeof f);
        //预求最大,先放最小
        memset(g, -0x3f, sizeof g);
    
        //区间DP的迭代式经典写法
        for (int len = 1; len <= n; len++) //枚举区间长度
            for (int l = 1; l + len - 1 <= n * 2; l++) {//枚举左端点
                //计算出右端点
                int r = l + len - 1;
                //如果区间内石子堆数是1的话,那么是不需要进行合并的,修改为值为0,base case
                if (len == 1) f[l][r] = g[l][r] = 0;
                else {
                    //枚举分界点k
                    for (int k = l; k < r; k++) {
                        f[l][r] = min(f[l][r], f[l][k] + f[k + 1][r] + s[r] - s[l - 1]);
                        g[l][r] = max(g[l][r], g[l][k] + g[k + 1][r] + s[r] - s[l - 1]);
                    }
                }
            }
        //因为从哪个位置断开环都是可行的,所以,我们依次检查一下
        int Min = INF, Max = -INF;
        for (int i = 1; i <= n; i++) {
            Min = min(Min, f[i][i + n - 1]);
            Max = max(Max, g[i][i + n - 1]);
        }
        //输出
        printf("%d\n%d\n", Min, Max);
        return 0;
    }
    
  • 相关阅读:
    二分法
    php冒泡排序
    php位运算
    php学习函数如何执行的
    php学习 打星星
    小程序的学习备注
    一个IP与多个域名绑定
    apache、mysql、php核心、phpmyadmin的安装及相互关联
    php虚拟主机配置( 输入网址 对应 ip地址)
    ORA-01084: OCI 调用中的参数无效
  • 原文地址:https://www.cnblogs.com/littlehb/p/15762598.html
Copyright © 2011-2022 走看看