P4216 [SCOI2015]情报传递 LCA+树上主席树 离线操作

题意:
给你一棵n个点的树,初始每个位置没有点权
有m次操作
1 x:让一个点从当前时刻开始,每秒操作点权++
2 x y c:查询一条链中有多少点的点权大于c
其中每秒操作点权++就是指我每操作一次,无论是否和那个点有
关,那个点权值都会++
1操作对于每个点只会开始一次
题解:
此题的做法非常多
大部分都是 O ( m ( l o g n ) 2 ) O(m(logn)^2) O(m(logn)2)的做法,也就是很朴素的树剖+线段树离线询问。
学习了一种 O ( m l o g n ) O(mlogn) O(mlogn)的做法,十分巧妙。
看到查询区间权值大于xxx的个数,肯定就是主席树了。
但是我们发现这个题目的点是不断更新的,但是主席树带修改的话就会多一个log,发现这个询问是离线的,那么我们可以先把情报员的点给存起来,然后一边建树的时候就给他加上即可。
如何建立主席树,在树上建立即可,每次查询是查询一条链的结点个数。
是从当前结点到根结点建立主席树。(原来的序列变成了许多条条链)
这样的话我们查询就可以先求出两个点的lca。
然后分别查询这两个点分别到lca这两条链上符合条件的点的个数,然后加起来即可。
代码:

#include<bits/stdc++.h>
#define endl '\n'
using namespace std;

const int maxn=4e5+10;

vector<int> edge[maxn];
int root;
int fa[maxn],top[maxn],son[maxn];
int dep[maxn],siz[maxn];
void dfs1(int u,int f){
    fa[u]=f;
    dep[u]=dep[f]+1;
    siz[u]=1;
    for(auto i:edge[u]){
        dfs1(i,u);
        siz[u]+=siz[i];
        if(siz[i]>siz[son[u]]) son[u]=i;
    }
}
void dfs2(int u,int t){
    top[u]=t;
    if(!son[u]) return ;
    dfs2(son[u],t);
    for(auto i:edge[u]){
        if(i==son[u]) continue;
        dfs2(i,i);
    }
}

int LCA(int x,int y){
    while(top[x]!=top[y]){
        if(dep[top[x]]<dep[top[y]]) swap(x,y);
        x=fa[top[x]];
    }
    return dep[x]<dep[y]?x:y;
}

int ls[maxn*21],rs[maxn*21];
int rt[maxn],sum[maxn*21];
int q,a[maxn],cnt;


void update(int &node,int start,int ends,int lst,int pos,int val){
    //cout<<"debug "<<start<<" "<<ends<<" "<<lst<<" "<<pos<<" "<<val<<endl;
    node=++cnt;
    ls[node]=ls[lst];
    rs[node]=rs[lst];
    sum[node]=sum[lst]+val;
    if(start==ends) return ;
    int mid=(start+ends)>>1;
    if(pos<=mid) update(ls[node],start,mid,ls[lst],pos,val);
    else update(rs[node],mid+1,ends,rs[lst],pos,val);
}
int query(int start,int ends,int l,int r,int x,int y){
    if(l<=start&&ends<=r){
        return sum[y]-sum[x];
    }
    int mid=start+ends>>1;
    int ans=0;
    if(l<=mid) ans+=query(start,mid,l,r,ls[x],ls[y]);
    if(mid<r) ans+=query(mid+1,ends,l,r,rs[x],rs[y]);
    return ans;
}
int pre[maxn];
void build(int x){
    if(a[x]!=0)  update(rt[x],1,q,rt[fa[x]],a[x],1);
    else  update(rt[x],1,q,rt[fa[x]],a[x],0);
    for(auto i:edge[x]){
        build(i);
    }
}
struct E{
    int x,y,c;
};
vector<E> v;
signed main(){
    ios::sync_with_stdio(false);
    cin.tie(0);
    int n;
    cin>>n;
    for(int i=1;i<=n;i++){
        int x;
        cin>>x;
        if(x==0) root=i;
        else edge[x].push_back(i);
    }
    cin>>q;
    for(int i=1;i<=q;i++){
        int opt;
        cin>>opt;
        if(opt==1){
            int x,y,c;
            cin>>x>>y>>c;
            c=i-c-1;
            v.push_back({x,y,c});
        }
        else{
            int t;
            cin>>t;
            a[t]=i;
        }

    }
    dfs1(root,0);
    dfs2(root,root);
    build(root);

    for(int i=0;i<v.size();i++){
        int ans=0;
        int x=v[i].x,y=v[i].y;
        int lca=LCA(x,y);

        //cout<<"lca  "<<lca<<" "<<x<<" "<<y<<endl;

        ans+=dep[x]+dep[y]-2*dep[lca]+1;
        //if(a[lca]!=0) ans++;
        cout<<ans<<" ";
        ans=0;

        //cout<<"debug   "<<lca<<" "<<v[i].c<<endl;
        if(v[i].c<=0){
            cout<<0<<endl;
            continue;
        }
        ans+=query(1,q,1,v[i].c,rt[lca],rt[x]);
        ans+=query(1,q,1,v[i].c,rt[lca],rt[y]);
        if(a[lca]<=v[i].c&&a[lca]!=0) ans++;
        cout<<ans<<endl;
    }

}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值