题意:
给出以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; }
时间: 2024-12-22 12:15:15