1. 程式人生 > >【題解:JSOI2008/luogu1197】星球大戰(並查集)

【題解:JSOI2008/luogu1197】星球大戰(並查集)

[傳送門]https://www.luogu.org/problemnew/show/P1197

題目描述
很久以前,在一個遙遠的星系,一個黑暗的帝國靠著它的超級武器統治著整個星系。

某一天,憑著一個偶然的機遇,一支反抗軍摧毀了帝國的超級武器,並攻下了星系中幾乎所有的星球。這些星球通過特殊的以太隧道互相直接或間接地連線。

但好景不長,很快帝國又重新造出了他的超級武器。憑藉這超級武器的力量,帝國開始有計劃地摧毀反抗軍佔領的星球。由於星球的不斷被摧毀,兩個星球之間的通訊通道也開始不可靠起來。

現在,反抗軍首領交給你一個任務:給出原來兩個星球之間的以太隧道連通情況以及帝國打擊的星球順序,以儘量快的速度求出每一次打擊之後反抗軍佔據的星球的連通塊的個數。(如果兩個星球可以通過現存的以太通道直接或間接地連通,則這兩個星球在同一個連通塊中)。

輸入輸出格式
輸入格式:
輸入檔案第一行包含兩個整數,N (1 < = N < = 2M1<=N<=2M) 和 M (1 < = M < = 200,0001<=M<=200,000),分別表示星球的數目和以太隧道的數目。星球用 0 ~ N-1 的整數編號。

接下來的 M 行,每行包括兩個整數 X, Y,其中( 0 < = X <> Y0<=X<>Y 表示星球 x 和星球 y之間有 “以太” 隧道,可以直接通訊。

接下來的一行為一個整數 k ,表示將遭受攻擊的星球的數目。

接下來的 kk 行,每行有一個整數,按照順序列出了帝國軍的攻擊目標。這 kk 個數互不相同,且都在 0 到n−1 的範圍內。

輸出格式:
第一行是開始時星球的連通塊個數。接下來的 K行,每行一個整數,表示經過該次打擊後現存星球的連通塊個數。

輸入輸出樣例
輸入樣例#1:
8 13
0 1
1 6
6 5
5 0
0 6
1 2
2 3
3 4
4 5
7 1
7 2
7 6
3 6
5
1
6
3
5
7
輸出樣例#1:
1
1
1
2
3
3

估計很多同學第一反應都是————tarjan!tarjan!!tarjan!!!
連通塊嘛,用tarjan既能計數,又能縮點,方便又具有很強的延展性,能與很多圖論演算法結合,有這麼好的演算法難道不用???何況這題基本就是一道板子板子板子啊!

上面對於tarjan演算法的形容詞是完全正確的,但是忽略了tarjan的一個缺點:難以動態維護


本題中的詢問是400,000規模的,肯定不能暴力地用tarjan來找連通塊,否則祝您TLE愉快。

所以,由連通性與動態維護,我們想到了用並查集
但是這也有個問題,用並查集做刪除的操作,不會很麻煩嗎?

誠然,並查集做刪除處理,確乎是麻煩的,這時我們就正難則反,逆向思維
如果給你一個圖,給你若干次插入的操作,問每次操作後的連通塊計數,並查集能不能做呢?
答案是顯然的,而且非常好做,相當方便。

正解就出來了:
我們進行離線處理,讀入完所有操作後再建圖(使用一個flag陣列紀錄各點有沒有被刪除過),這就是圖的終態,然後將k次操作逆向列舉,將刪除改為插入,這時用並查集實現插入與集數,存入ans陣列中,最後統一輸出答案。

程式碼:

#include<bits/stdc++.h>
using namespace std;
inline int read(){
	int ans;
	char ch;
	int flag=1;
	while((ch=getchar())<'0'||ch>'9') if(ch=='-') flag=-1;
	ans=ch-48;
	while((ch=getchar())>='0'&&ch<='9') ans=ans*10+ch-48;
	return ans*flag;
}
const int mod=1e9+7;
const int maxn=1e6+10;
const int maxm=2*maxn;
vector<int> g[maxn];
int n,m,k,fa[maxn],f[maxn],q[maxn],ans[maxn];
inline int find(int x){
	if(fa[x]==x) return fa[x];
	return fa[x]=find(fa[x]);
} 
inline void merge(int x,int y){
	int f1=find(x),f2=find(y);
	if(f1!=f2) fa[f2]=f1;
}
inline void write(int x){
	if(x<0) {
		putchar('-');x=-x;
	}
	if(x>9) write(x/10);
	putchar(x%10+'0');
}
int main(){
	n=read(),m=read();
	for(int i=1;i<=n;i++) fa[i]=i;
	for(int i=1;i<=m;i++){
		int x=read()+1,y=read()+1;//注意:題中點編號是0~n-1
		g[x].push_back(y),g[y].push_back(x);
	}
	k=read();
	for(int i=1;i<=k;i++) q[i]=read(),q[i]++,f[q[i]]=1;
	for(int i=1;i<=n;i++){
		if(!f[i]){
			for(int j=0;j<g[i].size();j++){
				if(f[g[i][j]]==0) merge(i,g[i][j]);
			}
		}
	}
	int cnt=0;
	for(int i=1;i<=n;i++){
		if(!f[i]&&fa[i]==i) cnt++;
	}
	for(int i=k;i>=1;i--){
		ans[i]=cnt;
		f[q[i]]=0;cnt++;
		int fy=q[i];
		for(int j=0;j<g[q[i]].size();j++){
			int v=g[q[i]][j];
			if(f[v]==1) continue;
			int fx=find(v);
			if(fx!=fy) {
				fa[fx]=fy;cnt--;
			}
		}
	}
	write(cnt);cout<<'\n';
	for(int i=1;i<=k;i++){
		write(ans[i]);
		cout<<"\n";
	} 
	return 0;
}