【并查集】BZOJ4551-[Tjoi2016&Heoi2016]树
NOIP太可怕了((( -口-)
【题目大意】
给定一颗有根树(根为1),有以下两种操作:
1. 标记操作:对某个结点打上标记(在最开始,只有结点1有标记,其他结点均无标记,而且对于某个结点,可以打多次标记。)
2. 询问操作:询问某个结点最近的一个打了标记的祖先(这个结点本身也算自己的祖先)
【思路】
正着做不行就反方向来。先离线处理所有操作,算出最终某个点被标记了几次。跑一次dfs算出最终状态时每个点最近的打了标记的祖先u[i]。
从后往前重新看操作,如果是标记操作,那么当前节点标记数-1,如果标记数为0了,那么u[i]=fa[i]最近的祖先(用并查集来处理之前的更新)。如果为询问,则输出当前最近的祖先(同样可以用并查集来做)。
1 #include<bits/stdc++.h> 2 using namespace std; 3 const int MAXN=100000+50; 4 vector<int> E[MAXN]; 5 int mark[MAXN]; 6 int query[MAXN],ans[MAXN]; 7 int u[MAXN],fa[MAXN],n,q; 8 char op[MAXN]; 9 10 void dfs(int x,int anc,int father) 11 { 12 fa[x]=father; 13 if (mark[x]>0) u[x]=x; 14 else u[x]=anc; 15 for (int i=0;i<E[x].size();i++) 16 { 17 int to=E[x][i]; 18 if (to==fa[x]) continue; 19 dfs(to,u[x],x); 20 } 21 } 22 23 int union_set(int x) 24 { 25 int r=x; 26 while (u[r]!=r) r=u[r]; 27 int now=x; 28 while (u[now]!=r) 29 { 30 int tmp=u[now]; 31 u[now]=r; 32 now=tmp; 33 } 34 return u[x]; 35 } 36 37 void init() 38 { 39 scanf("%d%d",&n,&q); 40 for (int i=1;i<n;i++) 41 { 42 int u,v; 43 scanf("%d%d",&u,&v); 44 E[u].push_back(v); 45 E[v].push_back(u); 46 } 47 mark[1]=1; 48 for (int i=1;i<=q;i++) 49 { 50 char tmp[1]; 51 scanf("%s %d",tmp,&query[i]); 52 if (tmp[0]=='C') mark[query[i]]++; 53 op[i]=tmp[0]; 54 } 55 dfs(1,1,0); 56 for (int i=1;i<=n;i++) cout<<u[i]<<endl; 57 } 58 59 void solve() 60 { 61 memset(ans,0,sizeof(ans)); 62 for (int i=q;i>=1;i--) 63 { 64 int now=query[i]; 65 if (op[i]=='C') 66 { 67 mark[now]--; 68 if (!mark[now]) now=union_set(fa[now]); 69 } 70 else ans[++ans[0]]=union_set(now); 71 } 72 for (int i=ans[0];i>=1;i--) printf("%d\n",ans[i]); 73 } 74 75 int main() 76 { 77 init(); 78 solve(); 79 return 0; 80 }