1. 程式人生 > 其它 >[CF Contest] Web of Lies 題解

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