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;
}