洛谷P3066 [USACO12DEC]逃跑的Barn BarnRunning Away From… dfs序+主席树

题目链接:传送门

题目大意:
给出以 1 1 1号点为根的一棵有根树,问每个点的子树中与它距离小于等于 L L L的点有多少个。

在节点子树内,想到用dfs序。
询问距离 &lt; = L &lt;=L <=L的点,想到用权值线段树。
又因为这里询问的是区间内距离 &lt; = L &lt;=L <=L的点,因此要珂持久化。
QWQ我好蒻啊

考虑主席树的维护和查询:
d i s [ i ] dis[i] dis[i]表示第 i i i个节点到根的距离。
i i i棵主席树维护的是 d f s dfs dfs序为 1 ~ i 1~i 1i的节点的 d i s dis dis信息。
d f n [ x ] dfn[x] dfn[x]表示 d f s dfs dfs序中 x x x出现的位置。
o u t [ x ] out[x] out[x]表示 x x x的子树中 d f s dfs dfs序的最大值(即 x x x子树中 d f s dfs dfs序最大的节点的 d f s dfs dfs序)。
那么 x x x的子树的区间就是 [ d f n [ x ] , o u t [ x ] ] [dfn[x],out[x]] [dfn[x],out[x]]
以上套个 d f s dfs dfs序模板即可,不再多说。

建完主席树后,假设当前节点到根距离为 d i s [ x ] dis[x] dis[x]
则现在需要询问一段区间内有多少个节点到根距离 &lt; = d i s [ x ] + L &lt;=dis[x]+L <=dis[x]+L
考虑主席树的性质,主席树维护了前缀信息。
因此答案就是
( ( ( o u t [ x ] out[x] out[x]个节点有多少个到根距离 &lt; = d i s [ x ] + L &lt;=dis[x]+L <=dis[x]+L ) − ( )-( )( d f n [ x ] − 1 dfn[x]-1 dfn[x]1个节点有多少个到根距离 &lt; = d i s [ x ] + L &lt;=dis[x]+L <=dis[x]+L ) ) )
另外这里距离很大,离散化一下就珂以了qwq。

丑陋的代码:

#include<stdio.h>
#include<cstring>
#include<algorithm>
#include<queue>
#define re register int
#define rl register ll
using namespace std;
typedef long long ll;
ll read() {
	rl x=0,f=1;
	char ch=getchar();
	while(ch<'0' || ch>'9') {
		if(ch=='-')	f=-1;
		ch=getchar();
	}
	while(ch>='0' && ch<='9') {
		x=10*x+ch-'0';
		ch=getchar();
	}
	return x*f;
}
const int Size=200005;
const int LOG=20;
ll n,L,maxn,a[Size],b[Size],T[Size];
int cnt,tot,tim,head[Size],dfn[Size],out[Size];
struct Edge {
	int v,next;
	ll t;
} w[Size];
void AddEdge(int u,int v,ll t) {
	w[++cnt].v=v;
	w[cnt].t=t;
	w[cnt].next=head[u];
	head[u]=cnt;
}
void dfs(int x,ll d) {
	//a[i]表示dfs序为i的节点到根距离 
	dfn[x]=++tim;
	a[tim]=d;
	for(int i=head[x]; i; i=w[i].next) {
		int nxt=w[i].v;
		dfs(nxt,d+w[i].t);
	}
	out[x]=tim;
}
int ls[Size*LOG],rs[Size*LOG],sum[Size*LOG];
int update(int pre,int l,int r,int v) {
	int rt=++tot;
	ls[rt]=ls[pre]; rs[rt]=rs[pre];
	sum[rt]=sum[pre]+1;
	if(l<r) {
		int mid=(l+r)>>1;
		if(v<=mid) {
			ls[rt]=update(ls[pre],l,mid,v);
		} else {
			rs[rt]=update(rs[pre],mid+1,r,v);
		}
	}
	return rt;
}
int Query(int u,int v,int l,int r,int rt) {
	//主席树区间求和 
	if(u<=l && r<=v) {
		return sum[rt];
	}
	int mid=(l+r)>>1,ans=0;
	if(u<=mid)	ans+=Query(u,v,l,mid,ls[rt]);
	if(v>mid)	ans+=Query(u,v,mid+1,r,rs[rt]);
	return ans;
}
int main() {
	n=read();
	L=read();
	for(re i=2; i<=n; i++) {
		int p=read();
		ll t=read();
		AddEdge(p,i,t);
	}
	dfs(1,0);
	//离散化 
	memcpy(b,a,sizeof(a));
	sort(b+1,b+1+n);
	maxn=unique(b+1,b+1+n)-(b+1);
	for(re i=1; i<=n; i++) {
		int k=lower_bound(b+1,b+1+maxn,a[i])-b;
		//按照dfs序建出主席树 
		T[i]=update(T[i-1],1,maxn,k);
	}
	for(re i=1; i<=n; i++) {
		ll maxd=a[dfn[i]]+L;
		//查找一下a[dfn[i]]+L在去重后在离散化数组b中的位置 
		int alb=upper_bound(b+1,b+1+maxn,maxd)-b-1;
		int ans1=Query(1,alb,1,maxn,T[out[i]]);
		int ans2=Query(1,alb,1,maxn,T[dfn[i]-1]);
		printf("%d\n",ans1-ans2);
	}
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值