【题解】【洛谷P1090】【贪心】【数组、二叉堆】[NOIP2004 提高组] 合并果子 / [USACO06NOV] Fence Repair G

[NOIP2004 提高组] 合并果子 / [USACO06NOV] Fence Repair G

通往洛谷的传送门

题目描述

在一个果园里,多多已经将所有的果子打了下来,而且按果子的不同种类分成了不同的堆。多多决定把所有的果子合成一堆。

每一次合并,多多可以把两堆果子合并到一起,消耗的体力等于两堆果子的重量之和。可以看出,所有的果子经过 n − 1 n-1 n1 次合并之后, 就只剩下一堆了。多多在合并果子时总共消耗的体力等于每次合并所耗体力之和。

因为还要花大力气把这些果子搬回家,所以多多在合并果子时要尽可能地节省体力。假定每个果子重量都为 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(1n10000) ,表示果子的种类数。

第二行包含 n n n 个整数,用空格分隔,第 i i i 个整数 a i ( 1 ≤ a i ≤ 20000 ) a_i(1\leq a_i\leq 20000) ai(1ai20000) 是第 i i i 种果子的数目。

输出格式

一个整数,也就是最小的体力耗费值。输入数据保证这个值小于 2 31 2^{31} 231

输入输出样例

输入 #1

3 
1 2 9

输出 #1

15

提示

对于 30 % 30\% 30% 的数据,保证有 n ≤ 1000 n \le 1000 n1000

对于 50 % 50\% 50% 的数据,保证有 n ≤ 5000 n \le 5000 n5000

对于全部的数据,保证有 n ≤ 10000 n \le 10000 n10000

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;
}

最后,制作不易,希望大家多多点赞收藏,关注下微信公众号,谢谢大家的关注,您的支持就是我更新的最大动力!
公众号上会及时提供信息学奥赛的相关资讯、各地科技特长生升学动态、还会提供相关比赛的备赛资料、信息学学习攻略等。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

蓝胖子教编程

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值