[HEOI2016/TJOI2016]樹 Method 1
阿新 • • 發佈:2022-03-05
離線儲存、並查集
題目很容易懂,看起來很簡單,但是一看到資料範圍,臉上就掉色了:\(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; }