洛谷P4556 雨天的尾巴 树上差分+权值线段树

题目链接

https://www.luogu.org/problem/P4556

分析

对于树上链的操作,可以用树上差分来做;

由于粮食有种类,树上每个节点建一棵权值线段树来记录答案;

树上差分后要合并,用线段树合并来实现。

AC代码

#include <cstdio>
#include <iostream>

using namespace std;

inline int read() {
	int num = 0;
	char c = getchar();
	while (c < '0' || c > '9') c = getchar();
	while (c >= '0' && c <= '9')
		num = num * 10 + c - '0', c = getchar();
	return num;
}

const int maxn = 1e5 + 5, maxz = 1e5;

int head[maxn], eid, depth[maxn], f[maxn][20], root[maxn], tot, ans[maxn];

struct Edge {
	int v, next;
} edge[2 * maxn];

inline void insert(int u, int v) {
	edge[++eid].v = v;
	edge[eid].next = head[u];
	head[u] = eid;
}

void dfs(int u, int fa) {
	for (int i = 1; (1 << i) <= depth[u]; ++i)
		f[u][i] = f[f[u][i - 1]][i - 1];
	for (int p = head[u]; p; p = edge[p].next) {
		int v = edge[p].v;
		if (v == fa) continue;
		depth[v] = depth[u] + 1, f[v][0] = u;
		dfs(v, u);
	}
}

inline int lca(int x, int y) {
	if (depth[x] < depth[y]) swap(x, y);
	for (int i = 18; i >= 0; --i)
		if (depth[x] - (1 << i) >= depth[y]) x = f[x][i];
	if (x == y) return x;
	for (int i = 18; i >= 0; --i)
		if (f[x][i] != f[y][i]) x = f[x][i], y = f[y][i];
	return f[x][0];
}

struct SegmentTree {
	int lc, rc, c, z;
} t[80 * maxn];

inline int newnode() {
	++tot;
	t[tot].lc = t[tot].rc = t[tot].c = t[tot].z = 0;
	return tot;
}

inline void up(int p) {
	t[p].c = max(t[t[p].lc].c, t[t[p].rc].c);
	t[p].z = t[t[p].lc].c >= t[t[p].rc].c ? t[t[p].lc].z : t[t[p].rc].z;
}

void modify(int p, int l, int r, int x, int d) {
	if (l == r) {
		t[p].c += d, t[p].z = t[p].c ? l : 0;
		return;
	}
	int mid = (l + r) >> 1;
	if (x <= mid) {
		if (!t[p].lc) t[p].lc = newnode();
		modify(t[p].lc, l, mid, x, d);
	} else {
		if (!t[p].rc) t[p].rc = newnode();
		modify(t[p].rc, mid + 1, r, x, d);
	}
	up(p);
}

int merge(int p, int q, int l, int r) {
	if (!p || !q) return p + q;
	if (l == r) {
		t[p].c += t[q].c, t[p].z = t[p].c ? l : 0;
		return p;
	}
	int mid = (l + r) >> 1;
	t[p].lc = merge(t[p].lc, t[q].lc, l, mid);
	t[p].rc = merge(t[p].rc, t[q].rc, mid + 1, r);
	up(p);
	return p;
}

void dfs_merge(int u, int fa) {
	for (int p = head[u]; p; p = edge[p].next) {
		int v = edge[p].v;
		if (v == fa) continue;
		dfs_merge(v, u), merge(root[u], root[v], 1, maxz);
	}
	ans[u] = t[root[u]].z;
}

int main() {
	int n = read(), m = read();
	for (int i = 1; i < n; ++i) {
		int u = read(), v = read();
		insert(u, v), insert(v, u);
	}
	dfs(1, 0);
	for (int i = 1; i <= n; ++i) root[i] = newnode();
	while (m--) {
		int x = read(), y = read(), z = read(), l, fa;
		l = lca(x, y), fa = f[l][0];
		modify(root[x], 1, maxz, z, 1), modify(root[y], 1, maxz, z, 1);
		modify(root[l], 1, maxz, z, -1);
		if (fa) modify(root[fa], 1, maxz, z, -1);
	}
	dfs_merge(1, 0);
	for (int i = 1; i <= n; ++i) printf("%d\n", ans[i]);
	return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值