【题解】【洛谷P1090】【贪心】[NOIP2004 提高组] 合并果子 / [USACO06NOV] Fence Repair G
[NOIP2004 提高组] 合并果子 / [USACO06NOV] Fence Repair G
题目描述
在一个果园里,多多已经将所有的果子打了下来,而且按果子的不同种类分成了不同的堆。多多决定把所有的果子合成一堆。
每一次合并,多多可以把两堆果子合并到一起,消耗的体力等于两堆果子的重量之和。可以看出,所有的果子经过 n − 1 n-1 n−1 次合并之后, 就只剩下一堆了。多多在合并果子时总共消耗的体力等于每次合并所耗体力之和。
因为还要花大力气把这些果子搬回家,所以多多在合并果子时要尽可能地节省体力。假定每个果子重量都为 1 1 1 ,并且已知果子的种类 数和每种果子的数目,你的任务是设计出合并的次序方案,使多多耗费的体力最少,并输出这个最小的体力耗费值。
例如有 3 3 3 种果子,数目依次为 1 1 1 , 2 2 2 , 9 9 9 。可以先将 1 1 1 、 2 2 2 堆合并,新堆数目为 3 3 3 ,耗费体力为 3 3 3 。接着,将新堆与原先的第三堆合并,又得到新的堆,数目为 12 12 12 ,耗费体力为 12 12 12 。所以多多总共耗费体力 = 3 + 12 = 15 =3+12=15 =3+12=15 。可以证明 15 15 15 为最小的体力耗费值。
输入格式
共两行。
第一行是一个整数
n
(
1
≤
n
≤
10000
)
n(1\leq n\leq 10000)
n(1≤n≤10000) ,表示果子的种类数。
第二行包含 n n n 个整数,用空格分隔,第 i i i 个整数 a i ( 1 ≤ a i ≤ 20000 ) a_i(1\leq a_i\leq 20000) ai(1≤ai≤20000) 是第 i i i 种果子的数目。
输出格式
一个整数,也就是最小的体力耗费值。输入数据保证这个值小于 2 31 2^{31} 231 。
输入输出样例
输入 #1
3
1 2 9
输出 #1
15
提示
对于 30 % 30\% 30% 的数据,保证有 n ≤ 1000 n \le 1000 n≤1000:
对于 50 % 50\% 50% 的数据,保证有 n ≤ 5000 n \le 5000 n≤5000;
对于全部的数据,保证有 n ≤ 10000 n \le 10000 n≤10000。
1.思路解析——贪心
强烈建议先去看【贪心延伸】哈夫曼编码和哈夫曼树。
根据上文中我们探讨出来的贪心策略,每一次将当前重量最小的两堆合并,使用一样的模型即可。但是要怎么用代码实现呢?
考虑使用两个数组。

先将数组1从小到大排序。可以发现,这样做有一个好处:合并后的果子(即数组2)一定是从小到大排序好的。
由于每一次合并时两个最小的数字不是在 数组 1 数组1 数组1就是在 数组 2 数组2 数组2。直接一个循环加比较就可以了。关键代码如下:
int n,a[MAXN], b[MAXN] ,i = 1, j = 1, s = 1; // 用i储存数组a的开头,j储存数组b的开头,s储存数组b的结尾
long long ans = 0;
for (int k = 1; k <= n - 1; k++){ // 会进行k-1次合并
// 取两个数组的最小值
int w = a[i] < b[j] && i <= n && j <= s ? a[i++] : b[j++];
w += a[i] < b[j] && i< = n && j <= s ? a[i++] : b[j++];
b[s++] = w; // 放到b数组后面
ans += w;
}
上面这段代码中取最小值使用了三目表达式,尽可能的压缩了代码长度。读者在使用的时候也可以尝试使用普通的if语句。
另外提一嘴,将整个数组内的所有元素设置为int类型的极大值可以使用类似于memset(a,127,sizeof(a))的语句。具体原理后面会讲解。
2.AC代码
#include<bits/stdc++.h>
using namespace std;
#define MAXN 10010
int main(){
//用i储存数组a的开头,j储存数组b的开头,s储存数组b的结尾
int n,a[MAXN],b[MAXN],i=1,j=1,s=1;
long long ans=0;
memset(a,127,sizeof(a));//初始化成INT_MAX
memset(b,127,sizeof(b));
cin>>n;
for(int i=1;i<=n;i++)cin>>a[i];
sort(a+1,a+n+1);
for(int k=1;k<=n-1;k++){//会进行k-1次合并
//取两个数组的最小值
int w=a[i]<b[j]&&i<=n&&j<=s?a[i++]:b[j++];
w+=a[i]<b[j]&&i<=n&&j<=s?a[i++]:b[j++];
b[s++]=w;//放到b数组后面
ans+=w;
}
cout<<ans;
return 0;
}
3.思路解析——堆(优先队列)
如果你不认识堆,那么现在可以走了。
这道题使用
S
T
L
STL
STL提供的priority直接爆杀。
4.AC代码
#include<bits/stdc++.h>
using namespace std;
int n,ans;
priority_queue<int,vector<int>,greater<int> >q;//定义一个小根堆
int main(){
cin>>n;
for(int i=1;i<=n;i++){
int tmp;
cin>>tmp;
q.push(tmp);
}
for(int i=1;i<=n-1;i++){
int w1=q.top();q.pop();//取两次最小值
int w2=q.top();q.pop();
ans+=w1+w2;
q.push(w1+w2);
}
cout<<ans;
return 0;
}
最后,制作不易,希望大家多多点赞收藏,关注下微信公众号,谢谢大家的关注,您的支持就是我更新的最大动力!
公众号上会及时提供信息学奥赛的相关资讯、各地科技特长生升学动态、还会提供相关比赛的备赛资料、信息学学习攻略等。

425

被折叠的 条评论
为什么被折叠?



