[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;
		}
	}
}
posted @ 2021-08-03 14:37  Inversentropir-36  阅读(59)  评论(0编辑  收藏  举报