牛客 情报传递 树剖+主席树

题目链接:https://ac.nowcoder.com/acm/problem/20570

  要统计X到Y上的点的数量以及危险值大于C的点的数量,对树上两点的路径进行查询可以用树链剖分,在某一个时间点t,我们要求出X到Y路径上危险值大于C的点的数量,我们可以把它转化为求在时间t-C-1之前开始收集情报的情报员数量,因为一旦开始收集危险值就会一直增加,所以在时间t-C-1之前开始收集情报的点的危险值一定是大于C的。

  这里我们可以使用主席树,我的做法是对每一个时间点都用动态开点建一棵线段树,如果k==2,那么在时间点为t的这颗树上,情报员T的值要赋值为1,说明情报员从t时间点开始收集情报了,如果k==1,那么我们需要的是查找,但是我这里因为要每一个时间点都创建一棵线段树,所以我们可以让root[t]=root[t-1],即让时间点为t的这个树指向时间点为t-1的这颗树的根节点,这样两颗树就是一模一样的了。然后再让时间点为0和t-C-1的这两颗线段树相减,两颗树两两对应的点相减,类似于求前缀和,我们只要用树剖查找X和Y路径上所有的点在两颗树相减之后得到的线段树(这颗线段树代表的时间就是1到t-C-1这一段时间)中值为1的个数就行了。

代码:

#include<iostream>
#include<cstring>
#include<algorithm>
#include<queue>
#include<map>
#include<stack>
#include<cmath>
#include<vector>
#include<set>
#include<cstdio>
#include<string>
#include<deque> 
using namespace std;
typedef long long ll;
#define eps 1e-8
#define INF 0x3f3f3f3f
#define maxn 200005
inline int read(){
    int f=1,x=0;char ch;
    do{ch=getchar();if(ch=='-')f=-1;}while(ch<'0'||ch>'9');
    do{x=x*10+ch-'0';ch=getchar();}while(ch>='0'&&ch<='9');
    return f*x;
}
struct node{
    int v,next;
}edge[maxn*2];
int n,m,t,cnt,boss,step,num,ans1,ans2;
struct Tree{
    int l,r;
    int sum;
}tree[maxn*25];
int head[maxn],fa[maxn],son[maxn],top[maxn],tot[maxn],L[maxn],dep[maxn],root[maxn];
void init(){
    memset(head,-1,sizeof(head));
    cnt=step=num=0;
}
void add(int u,int v){
    edge[++cnt].v=v;
    edge[cnt].next=head[u];
    head[u]=cnt;
}
void dfs(int u,int pre,int depth){
    dep[u]=depth;
    fa[u]=pre;
    tot[u]=1;
    int mx=0;
    for(int i=head[u];i!=-1;i=edge[i].next){
        int v=edge[i].v;
        if(v==pre) continue;
        dfs(v,u,depth+1);
        tot[u]+=tot[v];
        if(tot[v]>mx){
            mx=tot[v];
            son[u]=v;
        }
    }
}
void dfs2(int u,int pre){
    L[u]=++step;
    top[u]=pre;
    if(son[u])
    dfs2(son[u],pre);
    for(int i=head[u];i!=-1;i=edge[i].next){
        int v=edge[i].v;
        if(top[v]==0)
        dfs2(v,v);
    }
}
void update(int pre,int &root,int l,int r,int index){
    root=++num;
    tree[root]=tree[pre];
    tree[root].sum++;
    if(l==r) return;
    int mid=(l+r)/2;
    if(index<=mid)
    update(tree[pre].l,tree[root].l,l,mid,index);
    else
    update(tree[pre].r,tree[root].r,mid+1,r,index);
} 
int ask_interval(int root,int l,int r,int L,int R){ 
    if(L>R) return 0;
    if(l>=L&&r<=R){
        return tree[root].sum;
    }
    int mid=(l+r)/2;
    int ans=0;
    if(L<=mid)
    ans+=ask_interval(tree[root].l,l,mid,L,R);
    if(mid<R)
    ans+=ask_interval(tree[root].r,mid+1,r,L,R);
    return ans;
}
void ask(int x,int y,int c,int now){
    while(top[x]!=top[y]){
        if(dep[top[x]]<dep[top[y]]) swap(x,y);
        //只要是在时间[0,now-c-1)之间开始收集情报的情报员,都是危险的,这里没有写root[0],默认时间左边界就是0 
        if(now>c+1)
        ans2+=ask_interval(root[now-c-1],1,n,L[top[x]],L[x]);
        ans1+=L[x]-L[top[x]]+1;//dfs序相减的差就是这一段路径上的点的数量 
        x=fa[top[x]];
    }
    if(dep[x]>dep[y]) swap(x,y);
    if(now>c+1)
    ans2+=ask_interval(root[now-c-1],1,n,L[x],L[y]);
    ans1+=L[y]-L[x]+1;
}
int main()
{
    n=read();
    init();
    int u;
    for(int i=1;i<=n;i++){
        u=read();
        if(u==0)
        boss=i;
        else{
            add(u,i);
            add(i,u);
        }
    }
    dfs(boss,0,1);
    dfs2(boss,boss);
    m=read();
    int X,Y,C,K,T;
    //每个时间点都建一棵树,如果这个时间点有一个情报员开始收集情报,那么就把他在树上相应的dfs序编号节点加1 
    for(int i=1;i<=m;i++){
        K=read();
        if(K==1){
            X=read();
            Y=read();
            C=read();
            root[i]=root[i-1];//建一棵树,但是这个时间点建的树和前一个时间点的线段树是一模一样的 
            ans1=ans2=0;
            ask(X,Y,C,i);//查找一下X到Y需要的两个答案 
            printf("%d %d
",ans1,ans2);
        }else{
            T=read();//T开始收集情报了,把它在树上对应的节点标记为1 
            update(root[i-1],root[i],1,n,L[T]);//L[T]节点加1,表示这个位置在i时间点开始计数了 
        }
    }
    return 0;
}
原文地址:https://www.cnblogs.com/6262369sss/p/11754649.html