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;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值