(\)
Description
给出一颗树,开始只有 (1) 号节点有标记。
-
( C x) 对 (x) 号节点打标记
-
( Q x) 查询 (x) 号节点深度最深的有标记的祖先
(\)
Solution
-
链剖做法:
查询直到跳到第一个有权的重链上,线段树上二分即可。太板了不说了。
-
DFS序+线段树做法:
一遍DFS求出DFS序,子树大小以及节点深度。
用线段树维护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 ",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 ",q[i].ans); return 0; }