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