洛谷 P1090 & multiset运用

目录

题面

前言

思路

multiset的使用

头文件

声明

访问

操作

题解

参考文档


题面

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

题目描述

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

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

因为还要花大力气把这些果子搬回家,所以多多在合并果子时要尽可能地节省体力。假定每个果子重量都为 1 ,并且已知果子的种类 数和每种果子的数目,你的任务是设计出合并的次序方案,使多多耗费的体力最少,并输出这个最小的体力耗费值。

例如有 3 种果子,数目依次为 1 , 2 , 9 。可以先将 1 、 2 堆合并,新堆数目为 3 ,耗费体力为 3 。接着,将新堆与原先的第三堆合并,又得到新的堆,数目为 12 ,耗费体力为 12 。所以多多总共耗费体力 =3+12=15 。可以证明 15 为最小的体力耗费值。

输入格式

共两行。  
第一行是一个整数 n(1\le n\le10000) ,表示果子的种类数。  

第二行包含 n 个整数,用空格分隔,第 i 个整数 a_i(1\le a_i\le10000) 是第 i 种果子的数目。

输出格式

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

样例 #1

样例输入 #1

3
1 2 9

样例输出 #1
 

15

提示

对于 30\% 的数据,保证有 n\le1000

对于 50\% 的数据,保证有 n\le5000

对于全部的数据,保证有 n\le10000

前言

题目链接

这道题很古老了(NOIP2004),各种做法都很多,很多题解用的是priority_queue优先队列,但优先队列的维护机制有点反人类,其实有一种很简单的做法——用c++的STL multiset。

思路

首先我们看一下,很明显是贪心。基本上一眼就可以看出来每次都选最小的两个是最优情况。证明就留给读者们思考了。

最暴力的做法是插排维护有序性,但是显然,O(n^2)的时间复杂度并不能过。接着我们想到用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()插入元素,并维护有序性O(\log n)
erase()删除元素O(\log n),O(1),O(t)erase()有三种传参,分别对应三种复杂度
count()查找指定元素的数量O(\log n)
find()查找指定元素的迭代器O(\log n)未找到返回end()

equal_range()

upper_bound()

lower_bound()

二分查找函数O(\log n)
empty()判断multiset是否为空O(1)
size()取得multiset的元素个数O(1)
clear()清空multisetO(n)

另外,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)icon-default.png?t=O83Ahttps://www.apiref.com/cpp-zh/cpp/container/multiset.html迭代器 - OI Wiki (oi-wiki.org)icon-default.png?t=O83Ahttps://oi-wiki.org/lang/csl/iterator/

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值