「题解注释」P3345 [ZJOI2015] 幻想乡战略游戏

发布时间 2023-08-14 16:57:51作者: yi_fan0305

题解 P3345 【[ZJOI2015]幻想乡战略游戏】 - Baka's Blog - 洛谷博客 (luogu.org)

耗时:半个下午

代码注释:

#include <bits/stdc++.h>
typedef long long LL;

inline int rd() {
	int a = 1, b = 0; char c = getchar();
	while (!isdigit(c)) a = c == '-' ? 0 : 1, c = getchar();
	while (isdigit(c)) b = b * 10 + c - '0', c = getchar();
	return a ? b : -b;
}

const int N = 1e5 + 233;
int n, m;

struct Graph { int to, nxt, cost; } g[N * 2];
int head[N], tot;

inline void addedge(int x, int y, int c) {
	g[++tot].to = y, g[tot].nxt = head[x],
	g[tot].cost = c, head[x] = tot;
}

int fa[N], son[N], size[N], dep[N], dis[N];
int top[N], id[N], wh[N], wt[N], num;

void dfs1(int x) {
	for (int i = head[x]; i; i = g[i].nxt) {
		int y = g[i].to;
		if (y != fa[x]) {
			fa[y] = x;
			dep[y] = dep[x] + 1;
			dis[y] = dis[x] + g[i].cost;
			size[y] = 1;
			dfs1(y);
			if (size[son[x]] < size[y])
				son[x] = y;
			size[x] += size[y];
		}
	}
}

void dfs2(int x, int topf) {
	top[x] = topf; // 链顶
	id[x] = ++num; // 在线段树上的位置
	wh[num] = x; // 线段树上这个位置的元素
	wt[num] = dis[x] - dis[fa[x]]; // 当前节点到父节点的距离
	if (son[x]) {
		dfs2(son[x], topf);
		for (int i = head[x]; i; i = g[i].nxt) {
			int y = g[i].to;
			if (y != fa[x] && y != son[x])
				dfs2(y, y);
		}
	}
}

#define ls(p) p << 1
#define rs(p) p << 1 | 1

int sz[N * 4], tag[N * 4];
LL edge[N * 4], sum[N * 4];

void build(int p, int L, int R) {
	if (L == R) {
		edge[p] = wt[L];
		return;
	}
	int mid = (L + R) >> 1;
	build(ls(p), L, mid);
	build(rs(p), mid + 1, R);
	edge[p] = edge[ls(p)] + edge[rs(p)];
}

inline void pushup(int p) {
	sz[p] = std::max(sz[ls(p)], sz[rs(p)]);
	sum[p] = sum[ls(p)] + sum[rs(p)];
}

inline void pushdown(int p) {
	if (tag[p]) {
		sz[ls(p)] += tag[p];
		sz[rs(p)] += tag[p];
		tag[ls(p)] += tag[p];
		tag[rs(p)] += tag[p];
		sum[ls(p)] += (LL)tag[p] * edge[ls(p)];
		sum[rs(p)] += (LL)tag[p] * edge[rs(p)];
		tag[p] = 0;
	}
}

void add(int p, int l, int r, int v, int L, int R) { // 处理到新增加的长度
	if (l <= L && r >= R) {
		sz[p] += v; tag[p] += v;
		sum[p] += (LL)v * edge[p];
		return;
	}
	pushdown(p);
	int mid = (L + R) >> 1;
	if (l <= mid)
		add(ls(p), l, r, v, L, mid);
	if (r > mid)
		add(rs(p), l, r, v, mid + 1, R);
	pushup(p);
}

LL query(int p, int l, int r, int L, int R) { // 查询总长度
	if (l <= L && r >= R)
		return sum[p];
	pushdown(p);
	int mid = (L + R) >> 1;
	LL ret = 0;
	if (l <= mid)
		ret += query(ls(p), l, r, L, mid);
	if (r > mid)
		ret += query(rs(p), l, r, mid + 1, R);
	return ret;
}

inline int weight() { // 线段树上二分找重心
	int p = 1, L = 1, R = n;
	while (L < R) {
		pushdown(p);
		int mid = (L + R) >> 1;
		if (sz[rs(p)] * 2 >= sz[1])
			L = mid + 1, p = rs(p);
		else R = mid, p = ls(p);
	}
	return wh[L];
}

inline void range_add(int x, int y) { // 修改到链顶的 dis 总和
	while (top[x] != 1) {
		add(1, id[top[x]], id[x], y, 1, n);
		x = fa[top[x]];
	}
	add(1, 1, id[x], y, 1, n);
}

inline LL range_query(int x) { // 查询到链顶的 dis 总和,可以参考 lca 的深度求法
	LL ret = 0;
	while (top[x] != 1) {
		ret += query(1, id[top[x]], id[x], 1, n);
		x = fa[top[x]];
	}
	return ret + query(1, 1, id[x], 1, n);
}

LL sum_dis_e, sum_e;

inline LL getans(int x) {
	// dis[x, root] * e[y] + dis[rt, root] * e[y] - 2 * range(x, root)
	return sum_dis_e + dis[x] * sum_e - 2 * range_query(x);
}

int main() {
	n = rd(); m = rd();
	for (int i = 1; i < n; ++i) {
		int x = rd(), y = rd(), c = rd();
		addedge(x, y, c);
		addedge(y, x, c);
	}
	
	dfs1(1); // 求重儿子的 dfs
	dfs2(1, 1); // 重链剖分二阶段
	build(1, 1, n); // “摆树枝”
	
	while (m--) {
		int x = rd(), y = rd(); // 在 x 点上放 y 个军队
		sum_e += y; // 军队总和
		sum_dis_e += (LL)dis[x] * y; // 求总和
		range_add(x, y);
		printf("%lld\n", getans(weight()));
	}
	return 0;
}