【BZOJ3011】[Usaco2012 Dec]Running Away From the Barn【可并堆】

时间:2023-01-23 08:57:19

【题目链接】

pkusc时候做的题,现在发上来。


每个节点维护一个大根堆,维护从根节点到当前节点以及当前节点的子节点的路径长度(即深度)。

如果当前节点的堆的堆顶 - 当前节点的深度 大于L,那么删除堆顶,直到满足条件为止。

那么堆的size就是当前节点的答案。

当一个节点处理完毕后,把当前节点的堆与当前节点的父亲的堆合并,就可以了。

当一个节点的儿子都处理完后,那么就可以处理这个节点了,所以用类似拓扑序的思想,用一个队列搞。


可并堆用的左偏树。

/* Forgive me Not */
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <queue>

using namespace std;

typedef long long LL;

const int maxn = 200005;

int n, pre[maxn], size[maxn], ans[maxn], du[maxn], son[maxn][2], root[maxn];
LL L, w[maxn];
queue<int> q;

int dis[maxn];

inline void pushup(int x) {
	size[x] = size[son[x][0]] + size[son[x][1]] + 1;
}

inline int merge(int x, int y) {
	if(!x || !y) return x + y;
	if(w[x] < w[y]) swap(x, y);
	son[x][1] = merge(son[x][1], y);
	pushup(x);
	if(dis[son[x][0]] < dis[son[x][1]]) swap(son[x][0], son[x][1]);
	dis[x] = dis[son[x][1]] + 1;
	return x;
}

int main() {
	scanf("%d%lld", &n, &L);
	size[1] = 1; root[1] = 1; du[0] = -1;
	for(int i = 2; i <= n; i++) {
		scanf("%d%lld", &pre[i], &w[i]);
		w[i] += w[pre[i]];
		size[i] = 1;
		root[i] = i;
		du[pre[i]]++;
	}
	for(int i = 1; i <= n; i++) if(!du[i]) q.push(i);
	while(!q.empty()) {
		int u = q.front(); q.pop();
		for(; w[root[u]] - w[u] > L; root[u] = merge(son[root[u]][0], son[root[u]][1]));
		ans[u] = size[root[u]];
		root[pre[u]] = merge(root[pre[u]], root[u]);
		du[pre[u]]--;
		if(!du[pre[u]]) q.push(pre[u]);
	}
	for(int i = 1; i <= n; i++) printf("%d\n", ans[i]);
	return 0;
}