3129 樹

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;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章