【codeforces685B686D】【Kay and Snowflake】【线段树合并】

题目大意

给定30万个点的树,要求支持30万个询问,询问以某个点为根的子树的重心。

题解

给每的节点建立权值线段树,记录每个子节点为根的子树大小。重心为根的子树大小最接近但大于全树大小的一半。使用线段树合并即可解决本问题。

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=300000;
int cntgra,cntpon,n,q,to[maxn+10],next[maxn+10],begin[maxn+10],tsize[maxn*18*2+10],son[maxn*18*2+10][2],
    pon[maxn*18*2+10],ans[maxn+10],size[maxn+10];
void insert(int u,int v){
    to[++cntgra]=v;
    next[cntgra]=begin[u];
    begin[u]=cntgra;
}
int merge(int u,int v){
    if(!tsize[u])return v;
    if(!tsize[v])return u;
    son[u][0]=merge(son[u][0],son[v][0]);
    son[u][1]=merge(son[u][1],son[v][1]);
    tsize[u]=tsize[son[u][0]]+tsize[son[u][1]];
    return u;
}
int ask(int now,int l,int r,int tg){
    int m=(l+r)/2;
    if(!tsize[now])return 0;
    if(l==r)return pon[now];
    if(tg<=m){
        int tmp=ask(son[now][0],l,m,tg);
        if(tmp)return tmp;
        else return ask(son[now][1],m+1,r,tg);
    }
    else return ask(son[now][1],m+1,r,tg);
}
void change(int now,int l,int r,int tg,int v){
    int m=(l+r)/2;
    tsize[now]++;
    if(l==r)pon[now]=v;
    else if(tg<=m){
        if(!son[now][0])son[now][0]=++cntpon;
        change(son[now][0],l,m,tg,v);
    }
    else{
        if(!son[now][1])son[now][1]=++cntpon;
        change(son[now][1],m+1,r,tg,v);
    }
}
void dfs(int now){
    size[now]=1;
    for(int i=begin[now];i!=-1;i=next[i]){
        dfs(to[i]);
        size[now]+=size[to[i]];
        son[now][0]=merge(son[now][0],son[to[i]][0]);
        son[now][1]=merge(son[now][1],son[to[i]][1]);
    }
    change(now,1,n,size[now],now);
    ans[now]=ask(now,1,n,(size[now]+1)/2);
}
int main(){
    scanf("%d%d",&n,&q);
    memset(begin,255,sizeof(begin));cntpon=n;
    fo(i,2,n){
        int x;scanf("%d",&x);
        insert(x,i);
    }
    dfs(1);
     fo(i,1,q){
        int x;scanf("%d",&x);
        printf("%d\n",ans[x]);
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值