3129 树
Task
给定根为1,n个节点的树。有2种操作:
① 对x节点打标记
② 询问x最新一个打了标记的祖先
N,Q<=1e5
1在初始时已被标记。
Solution
暴力出奇迹
暴力一:O(1)打标记,一步步往上走,找到第一个打标记的祖先,只花了32MS。
暴力二:O(1)询问,打标记时dfs子树更新答案+剪枝:遇到某个子树已经打了标记,就不往下走了。
并查集
由于只有标记操作,没有删除标记操作,随着标记增多,每个点的答案不可能会变高,只会越来越矮。反之,如果只有删除标记操作,随着删除个数增多,每个点的答案会越来越高。这符合并查集“往上并”的思想。
如果离线从后往前进行删除操作,对于当前是标记操作且操作消失后点x不再被标记,那么x和x的子树的答案一定要往上并。
如果fa[i]表示i的父亲,f[i]表示离i最近的标记祖先,i失去标记后,i的答案等价于i的父亲fai的答案,合并。更改f[i]:f[i]=getfa(f[fa[i]])
const int M=1e5+3;
int f[M],fa[M],Q[M],cnt[M],head[M],ans[M];
bool ope[M];
int n,q,ecnt;
struct edge{
int t,nxt;
}e[M];
inline void addedge(int f,int t){
e[++ecnt]=(edge){t,head[f]};
head[f]=ecnt;
}
inline void input(){
int i,j,k,a,b,x;
char str[5];
rd(n);rd(q);
rep(i,1,n-1){
rd(a);rd(b);
fa[b]=a;
addedge(a,b);
}
fa[1]=1;cnt[1]=1;
rep(i,1,q){
scanf("%s",str);rd(x);
ope[i]=(str[0]=='Q');
Q[i]=x;
if(str[0]=='C')cnt[x]++;//打标记
}
}
inline int getfa(int x){
if(x!=f[x])f[x]=getfa(f[f[x]]);
return f[x];
}
inline void dfs(int x){
if(cnt[x])f[x]=x;
else f[x]=getfa(fa[x]);
for(int i=head[x];i;i=e[i].nxt) dfs(e[i].t);
}
inline void solve(){
int i,j,k,x;
dfs(1);
per(i,q,1){
x=Q[i];
if(ope[i])ans[i]=getfa(x);
else if(--cnt[x]==0)f[x]=getfa(fa[x]);
}
rep(i,1,q)
if(ope[i])sc(ans[i]);
}
int main(){
input();
solve();
return 0;
}