zoukankan      html  css  js  c++  java
  • AcWing 282. 石子合并

    题目传送门

    一、题目理解

    先来理解一下题意:\(1\ 3\ 5\ 2\) 这个例子,

    \(A\)方案:

    (1) 第一轮 \(1+3=4\) 变成 \(4\ 5\ 2\)

    (2) 第二轮 \(4+5=9\) 变成 \(9\)

    (3) 第三轮 \(9+2=11\)

    最小代价:\(4+9+11=24\)

    \(B\)方案:

    (1) 第一轮 \(1+3=4\) 变成 \(4\ 5\ 2\)

    (2) 第二轮 \(5+2=7\) 变成 \(4\ 7\)

    (3) 第三轮 \(4+7\)=\(11\)

    最小代价:\(4+7+11=22\)

    可以看出,不同的组合方式,确实最终的代价是不一样的。

    二、思考过程

    1、尝试一维描述

    万物皆\(dp\),一切状态都是由前面的状态转化而来。那么,如果用一维的方式来描述前面的状态,就是这样:\(f[i]\)。描述什么呢?似乎应该是从第\(1\)堆到第\(i\)堆的最小代价,如果能行的通,没有人会愿意把问题复杂化,再整什么二维概念了。

    下面采用实例法描述一下最简问题,看看这样设计的状态描述办法会不会有问题:

    • 如果有\(1\)堆石子,那么\(f[1]=0\)。为啥呢?一堆石子和谁合并啊,没的合并哪来的代价呢?

    • 如果有\(2\)堆石子,那么\(f[2]=a[1]+a[2]\)。为啥呢?一共两堆,不合并它们两个还能合并谁,代价也没啥最不最小了,就是两堆石子的个数和。

    • 如果有\(3\)堆石子,那么就有点意思了,可以\([(1)+(2)]+(3)\),也可以\((1)+[(2)+(3)]\),这时用\(f[i]\)怎么表示呢?我们发现,此时无法描述出\([(1)+(2)]\)\([(2)+(3)]\),这是一个的概念,一维的描述不出来!

    2、尝试二维描述

    现在来思考二维的描述法,比如\(f[i][j]\),描述就是从\(i\)堆到\(j\)堆的最小代价,依然是用最简例子思考一下:

    • 如果有\(1\)堆石子,那么\(f[1][1]=0\)

    • 如果有\(2\)堆石子,那么\(f[1][2]=a[1]+a[2]\)

    • 如果有\(3\)堆石子,\([(1)+(2)]+(3)\),也可以\((1)+[(2)+(3)]\)

    $f[1][3]=f[1][2]+f[2][3] + a[1]+a[2]+a[3] $

    为啥最后要加上\(a[1]+a[2]+a[3]\)呢?因为最终两堆合并时,本轮产生的代价就是两堆的石子和啊。

    三、状态转移方程

    \(i\)\(j\)区间内合并的最小代价,其实是在引入\(k\),其中\(i<=k<j\)后,考查每一个\(k\)作为中间结点来合并\(i\sim j\)的最小代价。

    每一轮合并的代价增加值=区间内的石子数量。

    如果每次都重新计算区间内的石子数量,性能太差,我们可以使用前缀和来优化。

    \(f[i][j] = min(f[i][j], f[i][k] + f[k + 1][j] + s[j] - s[i - 1])\)

    四、dfs实现代码

    #include <bits/stdc++.h>
    
    using namespace std;
    const int N = 1e3 + 10;
    const int INF = 0x3f3f3f3f;
    int a[N];
    int f[N][N];
    
    int dfs(int l, int r) {
        int &v = f[l][r];
        if (l == r) return v = 0;
        if (v != INF) return v;
        for (int i = l; i < r; i++)
            v = min(v, dfs(l, i) + dfs(i + 1, r) + a[r] - a[l - 1]);
        return v;
    }
    
    int main() {
        int n;
        cin >> n;
        //前缀和
        for (int i = 1; i <= n; i++) cin >> a[i], a[i] = a[i - 1] + a[i];
        memset(f, 0x3f, sizeof(f));
        cout << dfs(1, n) << endl;
        return 0;
    }
    
    

    五、DP实现代码

    #include <bits/stdc++.h>
    
    using namespace std;
    
    const int N = 3010;
    const int INF = 0x3f3f3f3f;
    
    int n;
    int s[N];//前缀和
    int f[N][N];//表示将i到j合并成一堆的方案的集合,属性:最小值
    // 石子合并
    int main() {
        cin >> n;
        //一维前缀和,为了计算sum(i,j)的值
        for (int i = 1; i <= n; i++) cin >> s[i], s[i] += s[i - 1];
    
        memset(f, 0x3f, sizeof f);  //预求最小,先设最大
        for (int i = 1; i <= n; i++)f[i][i] = 0;//第i堆与第i堆是不需要合并的,代价为0
    
        // 长度为1的区间不用枚举,区间长度是1的话,表示只有一堆,一堆不需要合并,代价是0
        for (int len = 2; len <= n; len++)           //枚举区间长度
            for (int i = 1; i + len - 1 <= n; i++) { //枚举区间左端点
                int j = i + len - 1;                 //区间的右端点
                //最后一个中间点K,需要枚举
                for (int k = i; k < j; k++)
                    f[i][j] = min(f[i][j], f[i][k] + f[k + 1][j] + s[j] - s[i - 1]);
            }
        cout << f[1][n] << endl;
        return 0;
    }
    

    六、精讲视频

    https://www.bilibili.com/video/av329239536/

  • 相关阅读:
    算法>分支限界 小强斋
    C# DataGridView 的 CellValueChanged 与修改数据没保存的情况
    Windows8使用虚拟磁盘vhdx功能来为容量较大的文件夹扩容
    DataSet / DataTable 对 Access 数据库进行更改后,无法获取自动编号(自增)列的新值
    使用Windows Server 2012配置更新服务Update Service,以及客户端的配置
    在Windows 8中找回开始菜单
    DataSet / BindingSource / DataGridView / BindingNavigator 的关系与绑定、更新顺序
    Windows8 的搜狗输入法的快捷键推荐设置方法
    如果要使用DataAdapter来修改DataSet的子集时,请尽量对父级做修改。
    about PostgreSQL
  • 原文地址:https://www.cnblogs.com/littlehb/p/15457225.html
Copyright © 2011-2022 走看看