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\n",cha[i]); }