题目链接: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;
}