您的位置:首页 > 其它

bzoj-3011 Running Away From the Barn

2015-08-25 22:34 337 查看
题意:

给出以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
[2],dis
,size
;
int out
,fa
,root
,ans
;
ll L
;
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;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: