【bzoj4551】【Tjoi2016】【Heoi2016】【树】【线段树】

题目大意

给出一棵有根树,有很多操作,给一个点打标记,查询到跟路上最近那个点打了标记。

题解

我的做法是先搞出dfs序,打标记只会影响子树,线段树区间修改就行了,查询就是单点查询。注意空点不下传标记,不然空间不够。
更好的做法是离线用并查集维护。打标记相当于把一棵子树从整棵树中断开,倒着做,先断开子树。查询就是getfather,答案就是father的father。修改就是合并子树,并查集连一下,应为修改可重复,遇到正数第一次修改才合并。

code

#include<set>
#include<cmath>
#include<cstdio>
#include<cstring>
#include<algorithm>
#define fo(i,j,k) for(int i=j;i<=k;i++)
#define fd(i,j,k) for(int i=j;i>=k;i--)
using namespace std;
int const maxn=100000;
int n,q,gra,time,to[maxn*2+10],next[maxn*2+10],begin[maxn+10],dfn[maxn+10],leave[maxn+10],pon[maxn+10],
    ans[maxn*5+10],tag[maxn*5+10];
void insert(int u,int v){
    to[++gra]=v;
    next[gra]=begin[u];
    begin[u]=gra;
}
void dfs(int now,int pre){
    dfn[now]=++time;
    pon[time]=now;
    for(int i=begin[now];i;i=next[i])
        if(to[i]!=pre)dfs(to[i],now);
    leave[now]=time;
}
void pushtag(int now,int tg){
    if(tag[now]){
        ans[now]=max(ans[now],tag[now]);
        if(tg){
            tag[now*2]=max(tag[now*2],tag[now]);
            tag[now*2+1]=max(tag[now*2+1],tag[now]);
        }
        tag[now]=0;
    }
}
void change(int now,int l,int r,int l1,int r1,int val){
    pushtag(now,l!=r);
    int m=(l+r)/2;
    if((l==l1)&&(r==r1))tag[now]=max(tag[now],val);
    else if(r1<=m)change(now*2,l,m,l1,r1,val);
    else if(m<l1)change(now*2+1,m+1,r,l1,r1,val);
    else{
        change(now*2,l,m,l1,m,val);
        change(now*2+1,m+1,r,m+1,r1,val);
    }
}
int get(int now,int l,int r,int pos){
    pushtag(now,l!=r);
    int m=(l+r)/2;
    if(l==r)return ans[now];
    else if(pos<=m)return get(now*2,l,m,pos);
    else return get(now*2+1,m+1,r,pos);
}
int main(){
    scanf("%d%d",&n,&q);
    fo(i,1,n-1){
        int u,v;scanf("%d%d\n",&u,&v);
        insert(u,v);insert(v,u);
    }
    dfs(1,0);
    change(1,1,n,1,n,1);
    fo(i,1,q){
        char oper;int num;scanf("%c%d\n",&oper,&num);
        if(oper=='C')change(1,1,n,dfn[num],leave[num],dfn[num]);
        else printf("%d\n",pon[get(1,1,n,dfn[num])]);
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值