信息学奥赛一本通 1372:小明的账单

【题目链接】

ybt 1372:小明的账单

【题目考点】

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

猜你喜欢

转载自blog.csdn.net/lq1990717/article/details/128502439