\(\\\)node
給出一顆樹,開始只有 \(1\) 號節點有標記。ios
\(\ C\ x\) 對 \(x\) 號節點打標記c++
\(\ Q\ x\) 查詢 \(x\) 號節點深度最深的有標記的祖先git
\(\\\)優化
鏈剖作法:ui
查詢直到跳到第一個有權的重鏈上,線段樹上二分便可。太板了不說了。spa
DFS序+線段樹作法:指針
一遍DFS求出DFS序,子樹大小以及節點深度。code
用線段樹維護DFS序,每一個節點記錄覆蓋當前區間深度最深的節點編號。標記下放的時候只需選擇深度更深的做爲答案便可。注意設置根節點的深度,不然第一次的全局標記可能會無效。遞歸
由於DFS序中一棵子樹是連續的,因此標記能夠看做整棵子樹的區間覆蓋操做。查詢也很方便,在遞歸查找時下放標記便可。
#include<cmath> #include<cstdio> #include<cctype> #include<cstdlib> #include<cstring> #include<iostream> #include<algorithm> #define N 100010 #define gc getchar #define Rg register #define mid ((l+r)>>1) using namespace std; inline int rd(){ int x=0; bool f=0; char c=gc(); while(!isdigit(c)){if(c=='-')f=1;c=gc();} while(isdigit(c)){x=(x<<1)+(x<<3)+(c^48);c=gc();} return f?-x:x; } int n,m,tot,hd[N]; int cnt,s[N],d[N],dfn[N],sz[N]; struct edge{int to,nxt;}e[N]; inline void add(int u,int v){ e[++tot].to=v; e[tot].nxt=hd[u]; hd[u]=tot; } void dfs(int u,int fa){ dfn[u]=++cnt; s[cnt]=u; sz[u]=1; for(Rg int i=hd[u],v;i;i=e[i].nxt) if((v=e[i].to)!=fa){ d[v]=d[u]+1; dfs(v,u); sz[u]+=sz[v]; } } struct segment{ int root,ptr; inline int newnode(){return ++ptr;} struct node{int ls,rs,tag;}c[N<<1]; inline void build(int &rt,int l,int r){ rt=newnode(); if(l==r) return; build(c[rt].ls,l,mid); build(c[rt].rs,mid+1,r); } inline void pushdown(int rt){ if(d[c[rt].tag]>d[c[c[rt].ls].tag]) c[c[rt].ls].tag=c[rt].tag; if(d[c[rt].tag]>d[c[c[rt].rs].tag]) c[c[rt].rs].tag=c[rt].tag; c[rt].tag=0; } inline void updata(int rt,int l,int r,int L,int R,int p){ if(l>R||r<L) return; if(l>=L&&r<=R){ if(d[p]>d[c[rt].tag]) c[rt].tag=p; return; } if(c[rt].tag) pushdown(rt); if(L<=mid) updata(c[rt].ls,l,mid,L,R,p); if(R>mid) updata(c[rt].rs,mid+1,r,L,R,p); } inline int query(int rt,int l,int r,int p){ if(l==r) return c[rt].tag; if(c[rt].tag) pushdown(rt); if(p<=mid) return query(c[rt].ls,l,mid,p); else return query(c[rt].rs,mid+1,r,p); } }tree; int main(){ n=rd(); m=rd(); for(Rg int i=1,u,v;i<n;++i){ u=rd(); v=rd(); add(u,v); add(v,u); } d[1]=1; dfs(1,0); tree.build(tree.root,1,n); tree.updata(tree.root,1,n,1,n,1); char c; int x; while(m--){ c=gc(); while(!isalpha(c)) c=gc(); if(c=='Q') printf("%d\n",tree.query(tree.root,1,n,dfn[rd()])); else{x=rd();tree.updata(tree.root,1,n,dfn[x],dfn[x]+sz[x]-1,x);} } return 0; }
並查集作法:
咱們用並查集指針表明當前最近的標記節點的方向,開始都指向父節點。標記一個點只需直接將指針指向本身,查詢即找到集合表明元素。容易發現正着處理複雜度不對,由於要保證樹的形態正確,因此咱們不能使用並查集的優化方式。
時光倒流。開始先把全部的標記打上,其他的點指向父節點。倒着模擬,遇到打標記就撤銷標記,詢問就是找到集合表明元素。能夠使用路徑壓縮優化,由於只會撤銷標記,任意時刻集合的表明元素必然是集合內的最優答案。
注意一個節點可能被屢次打標記,在最後一次撤銷標記時咱們再將其與父節點 merge 在一塊兒。
#include<cmath> #include<queue> #include<cstdio> #include<cctype> #include<cstdlib> #include<cstring> #include<iostream> #include<algorithm> #define N 100010 #define R register #define gc getchar #define mid ((l+r)>>1) using namespace std; inline int rd(){ int x=0; bool f=0; char c=gc(); while(!isdigit(c)){if(c=='-')f=1;c=gc();} while(isdigit(c)){x=(x<<1)+(x<<3)+(c^48);c=gc();} return f?-x:x; } int n,m,tot,hd[N],fa[N],cnt[N]; struct edge{int to,nxt;}e[N<<1]; inline void add(int u,int v){ e[++tot].to=v; e[tot].nxt=hd[u]; hd[u]=tot; } void dfs(int u){ for(R int i=hd[u],v;i;i=e[i].nxt) if((v=e[i].to)!=fa[u]){fa[v]=u;dfs(v);} } struct Q{int op,x,ans;}q[N]; struct UFS{ int f[N]; UFS(){memset(f,0,sizeof(f));} int find(int x){return x==f[x]?x:f[x]=find(f[x]);} inline void merge(int x,int fa){f[find(x)]=find(fa);} }ufs; int main(){ n=rd(); m=rd(); for(R int i=1,u,v;i<n;++i){ u=rd(); v=rd(); add(u,v); add(v,u); } fa[1]=1; dfs(1); char c; for(R int i=1;i<=m;++i){ c=gc(); while(!isalpha(c)) c=gc(); q[i].op=(c=='C'); q[i].x=rd(); if(q[i].op){ufs.f[q[i].x]=q[i].x;++cnt[q[i].x];} } for(R int i=1;i<=n;++i) if(!ufs.f[i]) ufs.f[i]=fa[i]; for(R int i=m;i;--i) if(q[i].op){ --cnt[q[i].x]; if(!cnt[q[i].x]) ufs.merge(q[i].x,fa[q[i].x]); } else q[i].ans=ufs.find(q[i].x); for(R int i=1;i<=m;++i) if(!q[i].op) printf("%d\n",q[i].ans); return 0; }