题意:
给出以1号点为根的一棵有根树,问每个点的子树中与它距离小于等于L的点有多少个。
n<=200000;
题解:
这题比较有意思;
首先考虑的就是树形DP,但是DP完全无法转移;
考虑一个结点的答案,那就是子树中与这个结点距离小于等于L的点数(废话);
那它父亲在这个子树的答案呢?
子树中每个点的距离都增加了,而相对大小关系没有改变;
所以就用一个可并堆来维护子树,每次将堆中距离过大的点pop掉;
堆中长度直接用这个点到1的长度,而到当前子树根的距离在减去子树根到1的长度就是了;
然后统计堆的大小,再将堆向上合并;
时间复杂度是O(nlogn),扫一遍树就出解了;
如果边权带负怎么办?
每个点可能加入堆中多次,但是如果搞一个对顶堆复杂度就不对了;
所以上启发式合并平衡树= =,O(nlog^2n)解决了;
代码:
#include<queue>
#include<stdio.h>
#include<string.h>
#include<algorithm>
#define N 210000
using namespace std;
typedef long long ll;
int ch[N][2],dis[N],size[N];
int out[N],fa[N],root[N],ans[N];
ll L[N];
queue<int>q;
void Pushup(int x)
{
size[x]=size[ch[x][0]]+size[ch[x][1]]+1;
}
int merge(int x,int y)
{
if(!x||!y) return x+y;
if(L[x]<L[y])
swap(x,y);
ch[x][1]=merge(ch[x][1],y);
Pushup(x);
if(dis[ch[x][0]]<dis[ch[x][1]])
swap(ch[x][0],ch[x][1]);
dis[x]=dis[ch[x][1]]+1;
return x;
}
int main()
{
int n,i,j,k,x,y;
ll m;
scanf("%d%lld",&n,&m);
dis[0]=-1,size[1]=1,root[1]=1;
for(i=2;i<=n;i++)
{
scanf("%d%lld",fa+i,L+i);
out[fa[i]]++,size[i]=1,root[i]=i;
L[i]+=L[fa[i]];
}
for(i=1;i<=n;i++)
{
if(out[i]==0)
q.push(i);
}
out[0]++;
while(!q.empty())
{
x=q.front(),q.pop();
while(L[root[x]]-L[x]>m)
root[x]=merge(ch[root[x]][0],ch[root[x]][1]);
ans[x]=size[root[x]];
y=fa[x];
root[y]=merge(root[y],root[x]);
out[y]--;
if(!out[y])
q.push(y);
}
for(i=1;i<=n;i++)
printf("%d\n",ans[i]);
return 0;
}