[CF Contest] Web of Lies 题解
题目描述
给你一个 \(n\) 个点 \(m\) 条边的无向图,要求支持以下几种操作共 \(q\) 次:
- 加入一条边 \((u,v)\)。
- 删除一条边 \((u,v)\)。
- 查询按照以下步骤对图进行操作后剩下的点的数量:删除所有与其相连的点编号都比其大的点和与该点相连的边,然后再次进行操作,直到删除点的数量为 \(0\) 为止。(该查询对图的结构不影响)
数据范围:\(1 \le n,q \le 2\cdot 10^5, 0 \le m \le 2\cdot 10^5\) 。
结论
对于每一个点,只要有任意相邻的点编号大于它,那么该点一定会在最后一轮前被删除。
证明
采用反证法。假如一个有一个相邻点编号大于它,且其存活到了最后一轮。那么其必有一个相邻点小于它。其相邻点若没有编号比其更小的,那么其会在本轮被删除,那本轮不为最后一轮。其相邻点若有编号比其更小的,同样的逻辑也适用于该点。这样我们得到一个无限下降的论证,但编号小于起始点的点数量是有限的,所以产生了矛盾。得证。
代码实现
由于正常邻接表的边删除操作不是 \(O(1)\) 的,所以不能使用邻接表。
我们可以考虑使用一个叫 alive
的数组来存储 “比编号为 \(i\) 的点编号大的其邻居的数目”,再使用一个变量 ans
存储每次询问的答案,由于初始所有点都是不连通的,那么 ans
初始化为 \(n\)。
- 假如一个点加边后有且仅有一个相邻的点编号大于它,答案就减少 \(1\)。
- 假如一个点删掉与它相邻且唯一的编号大于它的点,答案就增加 \(1\)。
代码
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
#include <vector>
using namespace std;
const int maxn = 2e5 + 5;
int alive[maxn], n, m;
int main() {
cin >> n >> m;
int ans = n;
for(int i = 1, x, y; i <= m; i ++) {
cin >> x >> y;
if(x > y) {
alive[y] ++;
if(alive[y] == 1) ans --;
} else {
alive[x] ++;
if(alive[x] == 1) ans --;
}
}
int q;
cin >> q;
while(q --) {
int cmd, x, y;
cin >> cmd;
if(cmd == 1) {
cin >> x >> y;
if(x > y) { // when x is stronger than y
alive[y] ++; // y's dangerous friend ++
if(alive[y] == 1) ans --; // if this is the first dangerous friend, answer --;
} else {
alive[x] ++;
if(alive[x] == 1) ans --;
}
} else if(cmd == 2) {
cin >> x >> y;
if(x > y) {
alive[y] --;
if(alive[y] == 0) ans ++;
} else {
alive[x] --;
if(alive[x] == 0) ans ++;
}
} else {
cout << ans << endl;
}
}
}