1. 程式人生 > 其它 >[HEOI2016/TJOI2016]樹 Method 1

[HEOI2016/TJOI2016]樹 Method 1

離線儲存、並查集

題目很容易懂,看起來很簡單,但是一看到資料範圍,臉上就掉色了:\(N\leq 10^5\)

冷靜分析:

上圖中我們設 \(ufs_x\) 的值就是此時 \(x\) 的最近標記祖先,\(pre_x\) 代表此時 \(x\) 的父親節點。操作分為:標記、查詢

對於標記而言:

如果說 \(x\) 被染色,那麼單獨對於 \(x\) 而言,他的最近標記祖先就是 \(x\);如果不是,那麼對於 \(x\) 而言,他的最近標記祖先就是 \(pre_x\) 的最近標記祖先。

由此我們可以得出求最近標記祖先,即查詢的程式碼:

void find(int x){
 if(ufs[x]==x) return x;
 else return find(pre[x]);
}

同樣的我們可以得出標記操作的修改是在 \(ufs_x\) 的基礎上完成的。於是我們可以設定 \(colcnt\) 陣列儲存第 \(x\) 個數被染了幾次色,如果染色此時大於 1,我們就可以將 \(ufs_x\) 賦值為自己,否則就將 \(ufs_x\) 設定為 \(pre_x\),其實這個設定為什麼數都可以,只要不使得像上面程式碼中的 \(ufs_x\)\(x\) 相等就可以了。

所以程式碼也就出來了:

  if(colcnt[x]) ufs[x]=x;   
  else ufs[x]=fa; 

現在我們還缺的是輸入和建立樹結構(即子節點和父節點),樹結構得用鏈式前向星和dfs

程式碼如下:

#include<bits/stdc++.h>
using namespace std; 
const int maxn=100010;
struct node{
	int next,v; 
}edge[maxn<<1];
int head[maxn],tot=0;
void addage(int u,int v){
	tot++;
	edge[tot].next=head[u];
	edge[tot].v=v;
	head[u]=tot;
}
int colcnt[maxn],pre[maxn],ufs[maxn];
struct line{
	int x,answer;
	bool typ=false;
}opt[maxn];
void dfs(int x,int fa){
	if(colcnt[x]) ufs[x]=x;
	else ufs[x]=fa;
	pre[x]=fa;
	for(int i=head[x];i;i=edge[i].next){
		int v=edge[i].v;
		if(v==fa) continue;
		dfs(v,x);
	}
} 
int find(int x){
	return x==ufs[x]?x:ufs[x]=find(ufs[x]);//必須這麼寫,要麼加快讀,也有可能快讀也不行,否則會TLE。
}
int main(){
	int N,Q;
	scanf("%d %d",&N,&Q);
	for(int i=1;i<N;i++){
		int u,v; 
		scanf("%d %d",&u,&v);
		addage(u,v),addage(v,u);
	} 
	colcnt[1]=1;
	for(int i=1;i<=Q;i++){
		char oper;
		cin>>oper;
		if(oper=='C'){
			scanf("%d",&opt[i].x);
			opt[i].typ=true;
			colcnt[opt[i].x]++;
		}
		else scanf("%d",&opt[i].x);
	}
	dfs(1,0);
	pre[1]=1;
	for(int i=Q;i>=1;i--){
		if(opt[i].typ){
			colcnt[opt[i].x]--;
			if(!colcnt[opt[i].x]) ufs[opt[i].x]=pre[opt[i].x];
		}
		else opt[i].answer=find(opt[i].x);
	}
	for(int i=1;i<=Q;i++)
		if(!opt[i].typ) printf("%d\n",opt[i].answer);
	return 0;
}