题意
给定一个编号为
1
1
,权重为 的点。在线完成下面两种操作(
Q
Q
个操作):
将一个权重为
W
W
的点加入到编号为 的节点之下。
2.
2.
询问从
R
R
开始,权重和不超过 的最长合法序列的长度。
其中一个合法的序列满足从
R
R
开始向祖先走,每当遇到一个权值大于等于目前序列最后一个点的权值时,都将它加入序列末端(构建序列的决策是单一的)。
思路
由于每个节点序列的单一性,不难想到存下它在序列中的后继,询问时不断向上寻找直至权值和即将超过
X
X
,复杂度 。
不难想到树上的倍增,每插入一个点,都处理出它序列中
2i
2
i
层后继,以及从这个点开始,长度为
2i
2
i
的子序列权值总和。其中
i=0
i
=
0
时,权值总和就是这个点的权值,而后继,如果连接的点的权值本身就大于自身,那
20
2
0
层后继就是这个点。否则需要在连接的点的序列中通过倍增找到第一个权值大于等于它的点。
i≠0
i
≠
0
时通过顺次递推得到。查询时,仍然倒置倍增循环并不断向上跳并在
X
X
上减,直到 无法继续减。有一个细节,查询时只跳
fa
f
a
不为
0
0
的点防止跳过头,但这个时候最多跳到根节点,因为指针指的是将要算的点,根节点只能跳至虚点零,所以它的贡献不会算入,最后要判一下能否再往上跳一格。
树上倍增就是如此,当直接向上寻找合法祖先复杂度过高时,通过记录 合法祖先及其信息,在询问时通过倍增跳跃寻找合法节点。它与二分查找的区别在于倍增只跳需要跳的点,这样就在复杂度上变为了
logn
log
n
,而非二分法的
(logn)2
(
log
n
)
2
。
代码
#include<iostream>
#include<cmath>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<algorithm>
#define FOR(i,x,y) for(int i=(x);i<=(y);i++)
#define DOR(i,x,y) for(int i=(x);i>=(y);i--)
#define N 400003
typedef long long LL;
using namespace std;
int fa[N][20],n=1;
LL w[N],sum[N][20];
void hooked(int a,int b) //a<-b
{
sum[b][0]=w[b];
if(w[a]>=w[b])fa[b][0]=a;
else
{
DOR(i,19,0)
if(fa[a][i]&&w[fa[a][i]]<w[b])
a=fa[a][i];
fa[b][0]=fa[a][0];
}
FOR(i,1,19)
if(fa[b][i-1])
{
fa[b][i]=fa[fa[b][i-1]][i-1];
sum[b][i]=sum[b][i-1]+sum[fa[b][i-1]][i-1];
}
}
int solve(int a,LL X)
{
int cnt=0;
DOR(i,19,0)
if(fa[a][i]&&sum[a][i]<=X)
{
X-=sum[a][i];
a=fa[a][i];
cnt+=(1<<i);
}
return cnt+(sum[a][0]<=X);
}
int main()
{
int T,lst=0;
scanf("%d",&T);
while(T--)
{
int op;LL p,q;
scanf("%d%lld%lld",&op,&p,&q);
if(op==1)
{
p^=lst,q^=lst;
w[++n]=q;
hooked(p,n);
}
else if(op==2)
{
p^=lst,q^=lst;
printf("%d\n",lst=solve(p,q));
}
}
return 0;
}