果子合并是如何将一堆果子合并起来所消耗体力最少,石子合并也是将一堆石子合并起来质量最小,但不同的是 石子合并只能相邻的两个合并 。本篇通过讲解这两个相似例题,来学习区间dp与贪心。
目录
石子合并:
题目:
思路:
代码:
果子合并
题目:
思路:
代码:
石子合并:
题目:
设有 N 堆石子排成一排,其编号为 1,2,3,…,N1,2,3,…,。
每堆石子有一定的质量,可以用一个整数来描述,现在要将这 N 堆石子合并成为一堆。
每次只能合并相邻的两堆,合并的代价为这两堆石子的质量之和,合并后与这两堆石子相邻的石子将和新堆相邻,合并时由于选择的顺序不同,合并的总代价也不相同。
例如有 44 堆石子分别为
1 3 5 2
, 我们可以先合并 1、21、2 堆,代价为 44,得到4 5 2
, 又合并 1、21、2 堆,代价为 99,得到9 2
,再合并得到 1111,总代价为 4+9+11=244+9+11=24;如果第二步是先合并 2、32、3 堆,则代价为 77,得到
4 7
,最后一次合并代价为 1111,总代价为 4+7+11=224+7+11=22。问题是:找出一种合理的方法,使总的代价最小,输出最小代价。
思路:
本题为区间合并经典例题,平时思路为,如何将两个两个石子分别合并(递归,从上向下),而在区间合并中, 应从结尾出发,将整堆石子分为两堆,再对这两堆继续分,直到分为一个个的石子(递推,从下向上)
在区间dp遍历时, 先遍历区间长度,再遍历区间左节点
代码:
#include<iostream>
#include<cmath>
using namespace std;
int n;
const int N = 310;
int s[N];//前缀和
int f[N][N];
int main(){
cin >> n;
for(int i = 1;i <= n;i++){
cin >> s[i];
s[i] += s[i - 1];
}
for(int len = 2;len <= n;len++){
for(int i = 1;i + len - 1 <= n;i++){
int l = i,r = i + len - 1;
f[l][r] = 1e8;
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]);
}
}
cout << f[1][n];
return 0;
}
果子合并
题目:
现在有n堆果子,第i堆有ai个果子。现在要把这些果子合并成一堆,每次合并的代价是两堆果子的总果子数。求合并所有果子的最小代价。
思路:
本题为哈夫曼树的应用,转换为求最短WPL(带权路径长度),运用贪心的思想。
每次选择价值最小和次小的那两个进行合并,合并成新的果子放进果堆中,然后又在果堆中选择最小的和次小的进行合并,这样下来,合并所有果子所花费的代价是最小的。
用 优先队列 来实现这个过程
代码:
#include <iostream>
#include <math.h>
#include <queue>
using namespace std;
int main()
{
int t,n,a;
scanf("%d",&t);
while(t--)
{
priority_queue<int,vector<int>,greater<int> >q;//优先队列,数值小的优先
scanf("%d",&n);
for(int i=0;i<n;i++)
{
scanf("%d",&a);
q.push(a);
}
int ans=0,temp;
while(q.size()>=2)//果堆中只有一个果子的时候合并就完成了
{
temp=0;
temp+=q.top(),q.pop();
temp+=q.top(),q.pop();//每次去优先队列队首的两个,因为他们是代价最小和次小的
ans+=temp;
q.push(temp);
}
printf("%d\n",ans);
}
return 0;
}