Blood Cousins Return(主席树 区间不同值的个数 二分 LCA)

http://codeforces.com/problemset/problem/241/F

题意: 有一片森林,点权,每次循环某个点的第 k k k层儿子中,点权的种数。

解析:

森林:让原坐标加一,让 1 1 1成为虚点就是一棵树了。

我按照层序遍历序号,将树变为数组,显然某个点的第 k k k层儿子在数组中连续。怎么找这个所在的区间呢?我们知道这个点的第 k k k层儿子所在层 d d d d d d层中的最小值 L L L和最大值 R R R,在其中进行两次二分即可。

然后相当于求区间不同值的个数。这个用主席树做,以原下标作为下标。

插入第 p p p棵树首先位置 p p p 1 1 1,如果前面出现与 p p p值相同的点,让那个点减 1 1 1。查询时,查第 r r r棵树的 [ l , r ] [l,r] [l,r]区间和即可。

补充:

二分去得到这个区间的复杂度为 O ( l o g 2 n ) O(log^2n) O(log2n),后来想到一种一个 l o g log log的做法。我们存下某个点的第一个儿子和最后一个儿子,那么就可以用倍增法得到第 k k k层儿子中的最小的和最大的了。

代码:

#include<bits/stdc++.h>
using namespace std;
#define LL long long
#define rep(i,a,b) for(int i=a;i<=b;i++)
#define per(i,a,b) for(int i=a;i>=b;i--)
#define debug(x) cerr<<#x<<":"<<x<<endl
const int maxn=1e5+9;
#define rep_e(i,p,u) for(int i=head[p],u=to[i];i;i=nex[i],u=to[i])
int head[maxn],to[maxn<<1],nex[maxn<<1],now;
void add(int a,int b){
    nex[++now]=head[a];head[a]=now;to[now]=b;
}
/*_________________________________________________________edge*/
unordered_map<string,int>M;
string x;
int n;
int val[maxn];
int dep[maxn];
int fa[maxn][20];
int rt;

int w[maxn],id[maxn],idd[maxn];
int l[maxn],r[maxn];
void bfs(){
    queue<int>Q;
    Q.push(rt);
    int ct=0;
    while(!Q.empty()){
        int p=Q.front();Q.pop();
        w[++ct]=val[p];
        id[p]=ct;
        idd[ct]=p;
        rep_e(i,p,u)Q.push(u);
    }
}
int root[maxn],ls[maxn*40],rs[maxn*40],siz[maxn*40];
int CT;
void insert(int last,int now,int l,int r,int pos,int val){
    siz[now]=siz[last]+val;
    ls[now]=ls[last];
    rs[now]=rs[last];
    if(l==r)return;
    int mid=(l+r>>1);
    if(pos<=mid)insert(ls[last],ls[now]=++CT,l,mid,pos,val);
    else insert(rs[last],rs[now]=++CT,mid+1,r,pos,val);
}
int query(int last,int now,int l,int r,int L,int R){
    if(l>=L&&r<=R){
        return siz[now]-siz[last];
    }
    int mid=l+r>>1;
    int res=0;
    if(L<=mid)res+=query(ls[last],ls[now],l,mid,L,R);
    if(R>mid)res+=query(rs[last],rs[now],mid+1,r,L,R);
    return res;
}


void dfs(int p,int d){
    dep[p]=d;
    l[d]=min(l[d],id[p]);
    r[d]=max(r[d],id[p]);
    rep_e(i,p,u){
        fa[u][0]=p;
        dfs(u,d+1);
    }
}
int vis[maxn];
void init(){
    for(int i=1;(1<<i)<=n;i++){
        rep(j,1,n){
            fa[j][i]=fa[fa[j][i-1]][i-1];
        }
    }
    rep(i,1,n){
        insert(root[i-1],root[i]=++CT,1,n,i,1);
        if(vis[w[i]]){
            int tmp=root[i];
            insert(tmp,root[i]=++CT,1,n,vis[w[i]],-1);
        }
        vis[w[i]]=i;
    }
}
int getFa(int p,int k){ // id[getFa] compare id[x]
    per(i,20,0){
        if((1<<i)<=k){
            p=fa[p][i];
            k-=(1<<i);
        }
    }
    return p;
}
int main(){
    ios::sync_with_stdio(false);
    cin.tie(0);
    cin>>n;
    rt=1;
    int ct=0;
    l[1]=1e9;
    rep(i,2,n+1){
        l[i]=1e9;
        cin>>x;
        if(M.count(x))val[i]=M[x];
        else val[i]=M[x]=++ct;
        int fa;cin>>fa;
        fa++;
        add(fa,i);
    }
    n++;
    bfs();
    dfs(rt,1);
    init();
    int q;cin>>q;
    while(q--){
        int p,k;cin>>p>>k;p++;
        int d=dep[p]+k;
        int L=l[d],R=r[d],ansL=1,ansR=1;
        while(L<=R){
            int mid=L+R>>1;
            if(id[getFa(idd[mid],k)]>=id[p])ansL=mid,R=mid-1;
            else L=mid+1;
        }
        L=l[d],R=r[d];
        while(L<=R){
            int mid=L+R>>1;
            if(id[getFa(idd[mid],k)]<=id[p])ansR=mid,L=mid+1;
            else R=mid-1;
        }
        if(ansL<l[d]||ansL>r[d]||ansR<l[d]||ansR>r[d]||getFa(idd[ansL],k)!=p){
            cout<<"0\n";
            continue;
        }
        cout<<query(0,root[ansR],1,n,ansL,ansR)<<'\n';
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值