luogu3066 逃跑的Barn(双指针)(树上差分)

版权声明:本文为博主原创文章,未经博主允许不得转载,除非先点了赞。 https://blog.csdn.net/A_Bright_CH/article/details/82558257

题意转换

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

题解

双指针+树上差分
对于每个点,看看它的贡献能往上去到哪里,把其中的点权值全部加1。这是一个“我为人人”的操作,如果让每个节点自己往下找,操作会相当复杂。因为往下是分叉,往上是合并。
容易想到用二分查找最大上限,再用树链剖分统计和。
实际上,有更优的解法。我们用指针法O(n)求最大上限,树上差分计和。

O(n)的做法,开一个栈,把这棵树的子链的所有节点编号存进去;同时开一个栈记录路径。
一个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;
}

猜你喜欢

转载自blog.csdn.net/A_Bright_CH/article/details/82558257