版权声明:本文为博主原创文章,未经博主允许不得转载,除非先点了赞。 https://blog.csdn.net/A_Bright_CH/article/details/82558257
题意转换
给出以1号点为根的一棵有根树,问每个点的子树中与它距离小于等于k的点有多少个。
题解
双指针+树上差分
对于每个点,看看它的贡献能往上去到哪里,把其中的点权值全部加1。这是一个“我为人人”的操作,如果让每个节点自己往下找,操作会相当复杂。因为往下是分叉,往上是合并。
容易想到用二分查找最大上限,再用树链剖分统计和。
实际上,有更优的解法。我们用指针法求最大上限,树上差分计和。
的做法,开一个栈,把这棵树的子链的所有节点编号存进去;同时开一个栈记录路径。
一个head指针,指向最大上限位置。当往深一层节点走时,找到一个层数最小的head,满足 son[head]~y的路径长 <=k。这个son[head]就是y的最大上限。
下来的树上差分就是模版使用了。
代码
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
typedef long long ll;
const int maxn=2e5+10;
ll n,K;
struct U{ll x,y,c,next;}e[maxn];int len=0,last[maxn];
void ins(ll x,ll y,ll c)
{
e[++len]=(U){x,y,c,last[x]};last[x]=len;
}
ll sum[maxn],fa[maxn];
void add(ll root,ll l,ll r)
{
sum[l]++;sum[r]++;sum[root]--;sum[fa[root]]--;
}
ll ans[maxn];
ll solve(ll x)
{
ans[x]=sum[x];
for(int k=last[x];k;k=e[k].next)
{
ll y=e[k].y;
ans[x]+=solve(y);
}
return ans[x];
}
ll dep,son[maxn],cc[maxn];
void dfs(ll x,ll now,ll dep,ll head)
{
son[dep]=x;
ll t_now=now,t_head=head;
add(son[head],son[head],son[dep]);
for(int k=last[x];k;k=e[k].next)
{
ll y=e[k].y;
fa[y]=x;
cc[dep]=e[k].c;
while(now+cc[dep]>K)
{
now-=cc[head];
head++;
}
now+=cc[dep];
dfs(y,now,dep+1,head);
now=t_now;head=t_head;
}
}
int main()
{
scanf("%lld%lld",&n,&K);
for(int i=2;i<=n;i++)
{
ll x,c;scanf("%lld%lld",&x,&c);
ins(x,i,c);
}
dfs(1,0,0,0);
solve(1);
for(int i=1;i<=n;i++) printf("%lld\n",ans[i]);
return 0;
}