首页 > 其他分享 >石子合并

石子合并

时间:2022-08-31 21:33:19浏览次数:80  
标签:int 石子 合并 两堆 代价 dp

石子合并

设有 $N$ 堆石子排成一排,其编号为 $1,2,3, \dots ,N$。

每堆石子有一定的质量,可以用一个整数来描述,现在要将这 $N$ 堆石子合并成为一堆。

每次只能合并相邻的两堆,合并的代价为这两堆石子的质量之和,合并后与这两堆石子相邻的石子将和新堆相邻,合并时由于选择的顺序不同,合并的总代价也不相同。

例如有 $4$ 堆石子分别为 1 3 5 2 , 我们可以先合并 $1$、$2$ 堆,代价为 $4$,得到 4 5 2 , 又合并 $1$,$2$ 堆,代价为 $9$,得到 9 2  ,再合并得到 $11$,总代价为 $4+9+11=24$;

如果第二步是先合并 $2$,$3$ 堆,则代价为 $7$,得到 4 7 ,最后一次合并代价为 $11$,总代价为 $4+7+11=22$。

问题是:找出一种合理的方法,使总的代价最小,输出最小代价。

输入格式

第一行一个数 N 表示石子的堆数 $N$。

第二行 $N$ 个数,表示每堆石子的质量(均不超过 $1000$)。

输出格式

输出一个整数,表示最小代价。

数据范围

$1 \leq N \leq 300$

输入样例:

4
1 3 5 2

输出样例:

22

 

解题思路

  区间dp。之前一直不知道什么时候用区间dp,经过一段时间后现在慢慢有点思路了。

  什么时候可以用区间dp呢?当一个问题可以分成两个部分(也可以是多个部分,下面都以两个部分的情况为例)来求解得到,且这两个部分的求解是相互独立的,也就是求解这一部分的结果并不会对另一部分的结果产生影响。同时这两个部分可以看作是相对于原问题规模更小的问题。因此如果要求整个问题的最大值,那么就分别求这两个部分的最大值,原问题的答案就是这两个部分的最大值之和。

  因此区间dp可以理解为分治加上记忆化搜索。

  以这一题为例,原问题就是求将$1 \sim n$的石子合并成一堆所需要的最小代价。每一次的合并都是将两堆石子合并成一堆,这启示我们一开始可以把整堆石子分成两大堆,根据两堆石子的边界进行划分,得到下面的集合划分:

  一共有$n-1$种划分的方式。并且任意两堆之间的求解是相互独立的。可以发现要合并$1 \sim n$这$n$堆石子,就是把划分得到的两大堆石子合并,这也是合并的最后一步,就会得到将所有石子合并成一堆的结果。因此答案就是枚举每个集合,看一下哪种划分方式得到的答案最小。那么如何求每个集合的最小值呢?与合并$n$堆石子一样,对这两堆的石子分别用同样的方式划分成两堆,然后枚举各个集合取最小值。

  定义状态$f(i,j)$表示所有将第$i$堆石子到第$j$堆石子合并成一堆石子的合并方式的集合。根据两堆石子的边界进行集合划分,因此状态转移方程就是$$f(i, j) = \min_{i \leq k < j} \{ {f(i,k) + f(k + 1, j) + s_{j} - s_{i - 1}} \}$$

  AC代码如下:

 1 #include <bits/stdc++.h>
 2 using namespace std;
 3 
 4 const int N = 310;
 5 
 6 int s[N];
 7 int f[N][N];
 8 
 9 int dp(int l, int r) {
10     if (f[l][r] != 0x3f3f3f3f) return f[l][r];
11     if (l == r) return f[l][r] = 0;
12     
13     for (int k = l; k < r; k++) {
14         f[l][r] = min(f[l][r], dp(l, k) + dp(k + 1, r) + s[r] - s[l - 1]);
15     }
16     
17     return f[l][r];
18 }
19 
20 int main() {
21     int n;
22     scanf("%d", &n);
23     for (int i = 1; i <= n; i++) {
24         scanf("%d", s + i);
25         s[i] += s[i - 1];
26     }
27     
28     memset(f, 0x3f, sizeof(f));
29     
30     printf("%d", dp(1, n));
31     
32     return 0;
33 }

  上面的代码是记忆化搜索来实现的,更为直观。也可以写成循环迭代的:

 1 #include <bits/stdc++.h>
 2 using namespace std;
 3 
 4 const int N = 310;
 5 
 6 int s[N];
 7 int f[N][N];
 8 
 9 int main() {
10     int n;
11     scanf("%d", &n);
12     for (int i = 1; i <= n; i++) {
13         scanf("%d", s + i);
14         s[i] += s[i - 1];
15     }
16     
17     memset(f, 0x3f, sizeof(f));
18     for (int len = 1; len <= n; len++) {
19         for (int i = 1; i + len - 1 <= n; i++) {
20             int j = i + len - 1;
21             if (len == 1) { // 区间长度为1的情况要特判
22                 f[i][j] = 0;
23             }
24             else {
25                 for (int k = i; k < j; k++) {
26                     f[i][j] = min(f[i][j], f[i][k] + f[k + 1][j] + s[j] - s[i - 1]);
27                 }
28             }
29         }
30     }
31     
32     printf("%d", f[1][n]);
33     
34     return 0;
35 }

 

参考资料

  AcWing 282. 石子合并(算法基础课):https://www.acwing.com/video/319/

标签:int,石子,合并,两堆,代价,dp
From: https://www.cnblogs.com/onlyblues/p/16644542.html

相关文章

  • 合并k个有序列表-python
    主要思路:借鉴堆、队列特点,构建新的有序结果#mergetheksortedlist#mainidea:将每个list放入队列,初始一个小顶堆,size为list个数,value为队列的首个元素,交替寻找最......
  • GroupBy中多列合并一列方法
    varquery=list.GroupBy(t=>new{t....分组字段}).Select(g=>new{FROM=g.Key.From,TO=g.Key.To,NUM=g.Count(),Time=g.Key.Time,Body=string.Join(",",g.Select(s=>......
  • 使用 Apollo Server 和 Koa 合并 GraphQL Schema
    使用ApolloServer和Koa合并GraphQLSchema今天,在我们现代的开发者世界中,完全无法想象没有React、NodeJS、GraphQL等技术的生活。他们拥有稳固的队伍,并在数据交......
  • vue.js3: 多张图片合并([email protected])
    一,安装用到的第三方库1,安装:liuhongdi@lhdpc:/data/vue/pdf/image2pdf$npmi-Svuedraggable@nextadded2packagesin11s2,查看已安装的版本:liuhongdi@lhd......
  • Git实战(四)| Git分支管理实操,搞定在线合并和本地合并
    ⬇️点击“下方链接”,提升测试核心竞争力!>>更多技术文章分享和免费资料领取类似于SVN这种集中式版本管理,三年前刚来上海工作时候,在华为驻场上班,华为用的就是SVN,印象最深......
  • redis zset 延迟合并任务处理
    rediszset延迟合并任务处理@AutowiredpublicRedisTemplateredisTemplate;##1.发送端:在接口中收集任务ID,累计时间段之后,合并处理。##rediszset主键,任......
  • 可选链操作符、逻辑与、空值合并运算符
    可选链操作符(?.)首先我们的明白一点,以下代码会报错吗?letobj={}leta=obj.nameconsole.log(a);那么,以下代码呢?letobj={}leta=obj.name.firstNameconsol......
  • 使用Element的table合并单元格(按相同项) 多列
    methods:{tableHeaderColor({row,column,rowIndex,columnIndex}){if(rowIndex===0){return"color:#4f81bd;font-weight:bold;";......
  • git分支合并到另一个分支
    git分支合并到另一个分支假设当前开发的分支为dev,要合并的目标分支为prd方式一:IDEA直接操作合并1、先提交本地dev分支上的代码到远程dev分支2、右击项目,Git——>Br......
  • LeetCode 21. 合并两个有序链表
    题目题目链接:https://leetcode.cn/problems/merge-two-sorted-lists/将两个升序链表合并为一个新的升序链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的......