Atcoder ABC(170) E 题题解

一.题意

自己 yy 的题意

有许多个幼儿园,有 \(N\) 个小朋友就读其中。每个小朋友有一个捣蛋值,一个幼儿园的捣蛋值为这个幼儿园里捣蛋值最大的小朋友,有一天, \(jw\) 来抽查这些学校,为了给 \(jw\) 留下好印象,这几所幼儿园的校长将推举出一所捣蛋值最小的幼儿园让 \(jw\) 去参观。可是小朋友们太捣蛋了,他们可能会从 C 幼儿园转到 D 幼儿园。每当一个小朋友转校,校长们就会去算 \(jw\) 将看到的捣蛋值,但小朋友们太捣蛋了,校长们光是管理就已经 玩不转了 忙的不可开交,于是他们把这个任务交给了你。

一句话题意

给你 \(2 * 10^5\) 个集合,\(i\) 集合内元素的最大值记作 \(val[i]\),第 \(i\) 次操作将 \(C_i\) 号元素转移到 \(D_i\),并询问所有非空集合的 \(val[i]\) 的最小值

二.题解

法一 优先队列+懒惰标记 (by lh

将每一个集合用大根堆(p1)来保存,再将 \(val_i\) 用小根堆(p2)保存。
将每一个操作分成6步走:
1.在 \(p2\) 中删除 \(val[id[c]]\) (\(id[x]\) 表示目前 \(x\) 号小朋友在哪个集合)
2.删除 \(p1[id[c]]\) 中的小朋友 \(c\)
3.在 \(p2\) 中加入 \(val[id[c]]\)
4.在 \(p2\) 中删除 \(val[id[d]]\)
5.在 \(p1[d]\) 中加入 \(c\) 号小朋友。
6.在 \(p2\) 中加入 \(val[id[d]]\)

非常清晰,我们只需要三种操作加入,查询和删除,\(p2\) 的操作和 \(p1\) 类似,所以这里只讲 \(p1\) 的操作

1.加入操作

添加操作非常好写,直接丢进去就可以了。

2.删除操作

由于无法高效的找到我们想要修改的元素,所以我们可以记录下需要修改的元素,当这个元素跑到堆顶时 \(pop\) 掉即可。

3.查询操作

一直 \(pop\) 直到堆顶元素不是需要删除的元素,取出堆顶元素即可。

思路很简单,但实现有点意思,具体细节看代码。

#include <map>
#include <set>
#include <cmath>
#include <queue>
#include <vector>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
#define LL long long
#define Abs(x) (x > 0 ? x : -x)
#define Min(x,y) (x < y ? x : y)
#define Max(x,y) (x > y ? x : y)

const int Maxn = 2 * 1e5;
const int Maxq = 2 * 1e5;
const int Maxm = 2 * 1e5;
const int Inf = 0x3f3f3f3f;

int n, q;
int val[Maxn + 5], id[Maxn + 5];
int res[Maxm + 5];
//val[i]表示 i号小朋友的捣蛋值 
//id[i]表示 i号小朋友在哪个幼儿园
//res[i]表示 i集合内的元素的最大值 

struct Node {
	int x, y;
	Node (int X, int Y) {
		x = X; y = Y;
	}
}; 
bool operator < (Node a, Node b) {
	if (a.x != b.x) return a.x > b.x;
	else return a.y > b.y;
}
priority_queue <pair <int, int> > que[Maxm + 5];
//每个集合的大根堆 
priority_queue <Node> ans;
//答案的小根堆 

signed main () {
	cin >> n >> q;
	for (int i = 1; i <= n; i++) {
		int x, y; cin >> x >> y;
		val[i] = x; id[i] = y;
		que[y].push (make_pair (x, i));
	}
	for (int i = 1; i <= Maxm; i++)
		if (que[i].size ()) {
			//非空就加入答案队列 
			res[i] = que[i].top ().first;
			ans.push (Node (que[i].top ().first, i));
		}
	while (q--) {
		int x, y; cin >> x >> y;
		//x号小朋友将转校到 y号幼儿园 
		
		while (que[id[x]].size () //集合非空 
		&& (id[que[id[x]].top ().second] != id[x] /*如果堆顶元素已经不在这个集合里*/ || que[id[x]].top ().second == x) /*或者堆顶元素为 x(将转到 y号幼儿园)*/) {
			que[id[x]].pop ();//弹出堆顶元素 
		}
		if (que[id[x]].size ())//如果该校还有小朋友 
			res[id[x]] = que[id[x]].top ().first;
		else//如果该校还有小朋友 
			res[id[x]] = Inf; //设为极大值,答案队列中将永远不会访问到 
		ans.push (Node (res[id[x]], id[x]));//加入答案队列 
		
		id[x] = y;//修改 x号小朋友的信息 
		
		que[y].push (make_pair (val[x], x));
		res[y] = que[y].top ().first;
		//修改 y号集合的信息(直接丢进去就行了) 
		
		ans.push (Node (res[y], y));//加入答案队列 
		
		while (res[ans.top ().y] != ans.top ().x /*如果堆顶元素已经被修改了*/) {
			ans.pop ();//弹出堆顶元素 
		} 
		cout << ans.top ().x << endl;
	}
	return 0;
}

法二 set (by yrl)

思路和法一一样,但是修改操作可以用 \(find\) + \(erase\),就不需要删除的懒标记了。
具体细节见代码。

//前置芝士:multiset的基本运用
#include <set>
#include <cstdio>
#include <utility>
#include <algorithm>

using namespace std;

const int Maxn = 2e5;

int n, q;
pair < int , int > a[Maxn + 5];
multiset < int > s[Maxn + 5], ans;
//《今天学了,所以要用》,有序无重集,用于存储 i 号幼儿园的婴儿评分

template < typename _T >
_T Abs (_T a) { return a < 0 ? - a : a; }
template < typename _T >
_T Max (_T a, _T b) { return a > b ? a : b; }
template < typename _T >
_T Min (_T a, _T b) { return a < b ? a : b; }
//没啥用的模板

int main () {
    scanf ("%d %d", &n, &q);

    for (int i = 1; i <= n; i ++) {
        scanf ("%d %d", &a[i].first, &a[i].second);
        s[a[i].second].insert (a[i].first);
        //存进去
    }

    for (int i = 1; i <= Maxn; i ++) {
        if (s[i].size ()) //该幼儿园有人
            ans.insert (*s[i].rbegin ()); //将答案集合初始化一下下
    }

    for (int i = 1, x, y; i <= q; i ++) {
        scanf ("%d %d", &x, &y);
        int u = a[x].first; //评分
        int v = a[x].second; //原幼儿园编号
        a[x].second = y; //important!已经转学了,记得更新幼儿园的编号

        s[v].erase (s[v].find (u)); //删除该婴儿的评分
        if (s[v].size () == 0 || *s[v].rbegin () < u) {
        /*1. v 幼儿园已经没人了,说明对答案没贡献了,并且删除前贡献的答案为 u ;
          2. v 幼儿园还有人,但最大评分没有 u 大,删除前答案也为 u  */

            ans.erase (ans.find (u)); //删除 u 对答案的贡献
            if (s[v].size ()) ans.insert (*s[v].rbegin ()); //若幼儿园还有人,更新对答案的贡献;没人则没有贡献
        }
        if (s[y].size () == 0 || *s[y].rbegin () < u) { 
        /*1. y 幼儿园本来没人,即原本对答案没贡献,则不需要删除对答案的贡献
          2. y 幼儿园有人,但最大评分比 u 小,即转学后 u 会对答案造成贡献  */

            if (s[y].size ()) ans.erase (ans.find (*s[y].rbegin ())); //删除贡献
            ans.insert (u); //更新贡献
        }
        s[y].insert (u); //将转学来的婴儿的评分放入集合中

        printf ("%d\n", *ans.begin ()); //输出答案集中的最小答案。OVER~
    }
    return 0;
}
posted @ 2021-07-07 18:59  C2022lihan  阅读(78)  评论(0编辑  收藏  举报