「USACO2012Dec」 Running Away From the Barn - 左偏树

题目描述

又到了FJ农场的挤奶时间了,但是奶牛都跑了!FJ需要把它们全部抓回来,并且需要你的帮助。

FJ的农场是由N(1≤N≤200000)个牧场组成,编号为1到N,由N-1条无向边连通。谷仓位于牧场1,并且从谷仓出发可以到达任何一个牧场。

FJ的奶牛今天早上都在它们的牧场,但没有人知道它们现在跑到哪里了。它们只能往远离谷仓的方向跑,由于它们太懒了,它们最多只能跑不超过L的距离。FJ想知道对于每一个牧场的奶牛能跑到的牧场的个数。

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

输入格式

第一行两个整数N和L (1 <= N <= 200,000, 1 <= L <= 10^18)

第2到N行,第i行有两个整数pi和Li, pi (1 <= pi < i)是i到谷仓路径上的第一个牧场,Li(1<=Li<=10^12)是这两个牧场之间的长度。

输出格式

输出共N行,每行一个整数,第i行表示第i个牧场能够到达的牧场的个数。

数据范围

100%的数据1 <= N <= 200,000, 1 <= L <= 10^18

分析

首先可以用暴力的方法,对每个点跑一遍Dfs,可做,但复杂度最坏可到 O ( n 2 ) O(n^2) O(n2)。想想其他算法?发现可以对每个点开一个左偏树,维护它的子树到这个该点的父亲节点的距离值,大根堆。在Dfs过程中,对于子树,先判断子树的堆里面的最大值是否大于L,若大于则出堆,直到小于等于;而后将该子树与该节点合并,并累加答案。递归完子树后,将该节点的左偏树的值全都加上该点到其父亲节点的距离,可以打标记,合并时下传标记。

这样有点麻烦,可以改为维护到根节点的距离值,子树判断时,条件改为堆顶的值-该节点的值是否大于L。这样就不需要打标记了。

代码

#include <iostream>
#include <cstdio>
#include <vector>
using namespace std;
const int N=200005;
typedef long long LL;
int nxt[N<<1],to[N<<1];
int h[N],cnt,n,ans[N],sz[N];
LL l,wei[N<<1],v[N],add[N];
int L[N],R[N],f[N],d[N];
void Add_Edge(int x,int y,LL z) {cnt++,nxt[cnt]=h[x],to[cnt]=y,wei[cnt]=z,h[x]=cnt;}
int Getf(int x) {return f[x]==x?x:f[x]=Getf(f[x]);}//找父亲
void Pushdown(int x) {v[L[x]]+=add[x],v[R[x]]+=add[x],add[L[x]]+=add[x],add[R[x]]+=add[x],add[x]=0;}
//标记下传
int Merge(int x,int y) {//合并子树
    if (!x||!y) return x+y;
    if (v[x]<v[y]) swap(x,y);
    Pushdown(x);
    Pushdown(y);
    R[x]=Merge(R[x],y);
    if (d[L[x]]<d[R[x]]) swap(L[x],R[x]);
    d[x]=d[R[x]]+1;
    return x;
}
void Dfs(int x,int fa,LL wp) {
    int now=x;
    for (int i=h[x];i;i=nxt[i]) {
        int y=to[i];
        if (y==fa) continue;
        Dfs(y,x,wei[i]);
        y=Getf(y);
        while (v[y]>l&&y) {//判断条件
            LL p=add[y],t=sz[y];
            f[y]=Merge(L[y],R[y]);
            f[f[y]]=f[y];
            y=f[y];
            if (f[y]) {
                v[y]+=p;
                add[y]+=p;
                sz[y]=t-1;
            }
        }
        y=Getf(to[i]);
        if (!y) continue;
        int t=sz[now]+sz[y];//合并子树
        f[now]=f[y]=Merge(now,y);
        now=f[now];
        sz[now]=t;
    }
    add[now]+=wp;//标记累加
    v[now]+=wp;
    ans[x]=sz[now];//更新答案 
}
int main() {
    scanf("%d%lld",&n,&l);
    for (int i=2;i<=n;i++) {
        int u;
        LL w;
        scanf("%d%lld",&u,&w);
        Add_Edge(i,u,w);
        Add_Edge(u,i,w);
    }
    d[0]=-1;
    for (int i=1;i<=n;i++) {
        f[i]=i;
        sz[i]=ans[i]=1;
        v[i]=d[i]=0;
    }
    Dfs(1,0,0);
    for (int i=1;i<=n;i++) {
        printf("%d\n",ans[i]);
    }
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值