【题目链接】
【题目考点】
1. 多重集合 multiset
2. 堆 priority_queue
【解题思路】
解法1:使用多重集合multiset
多重集合multiset原理是红黑树,相同的数据在multiset中可以保存多份。数据在multiset中是有序的,默认按升序排序,multiset中第一个元素是最小值,最后一个元素是最大值。
输入n,循环n次,把每天的账单数值加入到multiset中,每天选出multiset中的最大值和最小值,输出并删掉这两个值。
总添加的账单数达到 1 0 6 10^6 106(每天100个,一共15000天),删除的账单数达到 1 0 4 10^4 104个(每天删2个,一共15000天),红黑树中添加、删除元素的复杂度都为 O ( l o g n ) O(logn) O(logn),复杂度可以接受。
【注意】输出数据量较大,可以使用scanf/printf,或解除io同步,io解绑,使用’\n’。
解法2:使用堆
设账单类型,属性包括账单面额和账单编号。设大顶堆与小顶堆,里面保存的数据类型为账单类型。大顶堆中账单面额更大的更优先,小顶堆中账单面额更小的更优先。
输入数据,对于输入的每个账单,生成一个账单对象,给每个账单都分配唯一的编号,并把每个账单分别加入大顶堆和小顶堆。
设vis数组,vis[i]
表示编号为i的账单是否已经被支付。(最多有15000天,每天最多100个账单,因此vis数组的长度设为1500000)
每天选择支付账单的方法如下:
- 大顶堆中堆顶账单只要已经被支付,则删除堆顶,直到堆顶未被支付。那么此时堆顶账单为剩下的面额最大的账单,输出堆顶账单面额,将该账单设为已支付,删除堆顶。
- 小顶堆中堆顶账单只要已经被支付,则删除堆顶,直到堆顶未被支付。那么此时堆顶账单为剩下的面额最小的账单,输出堆顶账单面额,将该账单设为已支付,删除堆顶。
【题解代码】
解法1:使用多重集合multiset
#include<bits/stdc++.h>
using namespace std;
int main()
{
multiset<int> ms;//默认使用less<int>仿函数
int n, t, m;
scanf("%d", &n);
for(int i = 1; i <= n; ++i)
{
scanf("%d", &t);//这一行的数量
for(int j = 1; j <= t; ++j)
{
scanf("%d", &m);//输入账单钱数
ms.insert(m);
}
multiset<int>::iterator itFirst = ms.begin(), itLast = prev(ms.end());
printf("%d %d\n", *itFirst, *itLast);
ms.erase(itFirst);
ms.erase(itLast);
}
return 0;
}
解法2:使用堆
#include<bits/stdc++.h>
using namespace std;
#define N 1500005
struct Node
{
int money, i;//m:账单面额 i:账单编号
Node(){
}
Node(int a, int b):money(a), i(b){
}
};
struct SmallCmp//小顶堆比较仿函数
{
bool operator () (Node a, Node b)
{
return b.money < a.money;
}
};
struct BigCmp//大顶堆比较仿函数
{
bool operator () (Node a, Node b)
{
return b.money > a.money;
}
};
bool vis[N];//vis[i]:编号为i的账单是否已经支付
int main()
{
ios::sync_with_stdio(false);
cin.tie(0);
priority_queue<Node, vector<Node>, SmallCmp> small_pq;//小顶堆
priority_queue<Node, vector<Node>, BigCmp> big_pq;//大顶堆
int n, t, m, ind = 0;//ind:编号
cin >> n;
for(int i = 1; i <= n; ++i)
{
cin >> t;//这一行的数量
for(int j = 1; j <= t; ++j)
{
cin >> m;//输入账单钱数
ind++;//编号加1
small_pq.push(Node(m, ind));//小顶堆添加编号为ind的面值为m的账单
big_pq.push(Node(m, ind));//大顶堆添加编号为ind的面值为m的账单
}
while(vis[small_pq.top().i])//小顶堆中不断找面值最小的账单,如果已支付则删除
small_pq.pop();
cout << small_pq.top().money << ' ';
vis[small_pq.top().i] = true;
small_pq.pop();
while(vis[big_pq.top().i])//大顶堆中不断找面值最大的账单,如果已支付则删除
big_pq.pop();
cout << big_pq.top().money << '\n';
vis[big_pq.top().i] = true;
big_pq.pop();
}
return 0;
}