P3066 [USACO12DEC]逃跑的BarnRunning Away From (树上二分)

题意

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

树上二分.
这个做法还是基于树上差分的,也就是对于每一个点uu,我们要找到它向上跳LL的长度最高能够跳到的祖先.(当然倍增求出这个连dfsdfs都不用更加粗暴.)
因此我们不仅要记录每一个节点到根节点的距离disdis,还要记录每一个节点到根节点要经过边的边数,也即点的深度depdep.
然后再用tmptmp数组记录从根节点到uu经过的每一个点,tmp[i]tmp[i]表示从根节点到uu的路径上深度为ii的节点的编号,
只要用tmp[dep[u]]=u一句话就可以记录了.
记录了路径上的点,接下来神奇的事情发生了.
可以发现,路径上的点到根的距离是单调递增的,我们在tmptmp数组里二分可以找到离uu距离不超过LL的最高祖先. 这样对每个点都差分,最后dfsdfs处理一遍就可以了.

#include<bits/stdc++.h>
using namespace std;
#define ll long long
const int yuzu=2e5;
typedef int fuko[yuzu|10];
typedef ll rize[yuzu|10];
struct edge{int to; ll c;};
vector<edge> lj[yuzu|10];
fuko fa,ans,dep,tmp,cha;
rize dis;

void dfs(int u,ll len) {
dep[u]=dep[fa[u]]+1; // 求节点u的深度
tmp[dep[u]]=u; // 记录从根节点到u深度为dep[u]的点.
int l=1,r=dep[u],mid;
for (;l<r;dis[u]-dis[tmp[mid]]<=len?r=mid:l=mid+1) mid=l+r>>1;
/*在这条路径上二分能跳到的最高祖先*/
cha[fa[tmp[l]]]--,cha[u]++; // 差分
for (edge i:lj[u])
  dis[i.to]=dis[u]+i.c,dfs(i.to,len);
}

int main() {
int i,n; ll l,x;
scanf("%d%lld",&n,&l);
for (i=2;i<=n;++i)
  scanf("%d%lld",&fa[i],&x),
  lj[fa[i]].push_back(edge{i,x});
dfs(1,l);
for (i=n;i;--i) cha[fa[i]]+=cha[i];
/*由于每个点的父节点已知并且肯定比原节点编号小,可以倒过来用一个循环代替dfs*/
for (i=1;i<=n;++i) printf("%d
",cha[i]);
}
View Code
原文地址:https://www.cnblogs.com/shuaihui520/p/10484526.html