目录
题面
[NOIP2004 提高组] 合并果子 / [USACO06NOV] Fence Repair G
题目描述
在一个果园里,多多已经将所有的果子打了下来,而且按果子的不同种类分成了不同的堆。多多决定把所有的果子合成一堆。
每一次合并,多多可以把两堆果子合并到一起,消耗的体力等于两堆果子的重量之和。可以看出,所有的果子经过 次合并之后, 就只剩下一堆了。多多在合并果子时总共消耗的体力等于每次合并所耗体力之和。
因为还要花大力气把这些果子搬回家,所以多多在合并果子时要尽可能地节省体力。假定每个果子重量都为 ,并且已知果子的种类 数和每种果子的数目,你的任务是设计出合并的次序方案,使多多耗费的体力最少,并输出这个最小的体力耗费值。
例如有 种果子,数目依次为 , , 。可以先将 、 堆合并,新堆数目为 ,耗费体力为 。接着,将新堆与原先的第三堆合并,又得到新的堆,数目为 ,耗费体力为 。所以多多总共耗费体力 。可以证明 为最小的体力耗费值。
输入格式
共两行。
第一行是一个整数 ,表示果子的种类数。
第二行包含 个整数,用空格分隔,第 个整数 是第 种果子的数目。
输出格式
一个整数,也就是最小的体力耗费值。输入数据保证这个值小于 。
样例 #1
样例输入 #1
3
1 2 9
样例输出 #1
15
提示
对于 的数据,保证有 :
对于 的数据,保证有 ;
对于全部的数据,保证有 。
前言
这道题很古老了(NOIP2004),各种做法都很多,很多题解用的是priority_queue优先队列,但优先队列的维护机制有点反人类,其实有一种很简单的做法——用c++的STL multiset。
思路
首先我们看一下,很明显是贪心。基本上一眼就可以看出来每次都选最小的两个是最优情况。证明就留给读者们思考了。
最暴力的做法是插排维护有序性,但是显然,的时间复杂度并不能过。接着我们想到用set维护有序性,但是由于set自带去重,会导致结果错误。但是,multiset既能像set一样排序,也不会像set一样去重。
multiset的使用
头文件
首先,使用multiset必须先导入set头文件(用万能头的当我没说)
#include<set>
声明
multiset的声明方式和set类似,比如如下代码就定义了一个名叫m的multiset:
multiset<int>m;
访问
multiset和set不同,不允许用下标访问元素,只能通过迭代器访问元素。这里的迭代器既包含成员函数,也包含iterator类型变量。以下是四个成员迭代器函数:
begin() | 取得头元素的迭代器 |
end() | 取得尾元素的后继元素的迭代器(不是尾元素!!!) |
rbegin() | 取得尾元素的反向迭代器 |
rend() | 取得头元素的后继元素的反向迭代器(即头元素的前一个位置) |
在C++11及以上版本中,每个迭代器可以在开头加c修饰,表示返回一个常量迭代器。
注意:rend()虽然称为“头元素的后继元素”,实际上是头元素的前一个元素(即begin()的前驱),由于是反向迭代器,因此使用“后继”。
在C++11及以上版本中,可以使用next和prev函数获取元素的前驱和后继。
在 C++11 以后可以使用
std::next(it)
获得向前迭代器it
的后继(此时迭代器it
不变),std::next(it, n)
获得向前迭代器it
的第n
个后继。在 C++11 以后可以使用
std::prev(it)
获得双向迭代器it
的前驱(此时迭代器it
不变),std::prev(it, n)
获得双向迭代器it
的第n
个前驱。——摘自 OI Wiki
我们还可以使用迭代器循环遍历multiset(如下):
for(multiset<int>::iterator i = m.begin();i != m.end();i++){cout<<*i<<" ";}//C++11及以上可以简化成for(auto &i : m){cout<<i<<" ";}
因为迭代器名字比较长,C++11及以上建议用auto循环。
操作
multiset的操作与set大体相同。以下是常用的操作:
函数 | 说明 | 时间复杂度 | 特殊说明 |
insert() | 插入元素,并维护有序性 | ||
erase() | 删除元素 | erase()有三种传参,分别对应三种复杂度 | |
count() | 查找指定元素的数量 | ||
find() | 查找指定元素的迭代器 | 未找到返回end() | |
equal_range() upper_bound() lower_bound() | 二分查找函数 | ||
empty() | 判断multiset是否为空 | ||
size() | 取得multiset的元素个数 | ||
clear() | 清空multiset |
另外,multiset在C++20之前支持==,!=,<,<=,>,>=,<=>(按字典序),从C++20开始仅支持==。
题解
说了那么多,该来做题了。这道题要动态维护有序性(而且不能去重)。这恰好就是multiset的特性。具体操作如下伪代码:
Input();
for i in {1 to n-1} do:
var x <= m.begin();
erase(x);
var y <= next(x);
erase(y);
insert(x+y);
Output();
为了避免抄题嫌疑,AC代码就不放了。具体实现的时候有个细节要注意:erase()里面放的是迭代器,千万不要解引用!!!如果erase()传参是数字的话,函数会删除所有与传参相同的元素,而这道题数据很明显会出现重复。这就是multiset的一个大坑点!
搞定了这个细节,AC就很轻松了。
参考文档
std::multiset - C++中文 - API参考文档 (apiref.com)https://www.apiref.com/cpp-zh/cpp/container/multiset.html迭代器 - OI Wiki (oi-wiki.org)https://oi-wiki.org/lang/csl/iterator/