牛客 情报传递 树剖+主席树
作者:互联网
题目链接: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\n",ans1,ans2); }else{ T=read();//T开始收集情报了,把它在树上对应的节点标记为1 update(root[i-1],root[i],1,n,L[T]);//L[T]节点加1,表示这个位置在i时间点开始计数了 } } return 0; }
标签:pre,ch,树剖,情报,top,牛客,int,include,root 来源: https://www.cnblogs.com/6262369sss/p/11754649.html