[ZJOJ2015]幻想乡战略游戏[题解]

幻想乡战略游戏

\(Problem\)

给定一棵大小为 \(n\)带边权的树,一开始,每个节点的点权为 \(0\)

给定 \(Q\) 次操作,每次操作将节点 \(u\) 的点权增加或减少 \(x\),保证每次操作后点权大于等于 \(0\)

每次操作后,需要你回答如下问题:

  • 你可以任意指定一个点 \(x\),则 \(val = \sum _{i = 1}^n dis(x, i)\times w_i\),其中 \(w_i\) 表示点 \(i\) 的点权,\(dis(x, i)\) 表示节点 \(x\)\(i\) 的距离,输出最小的 \(val\)

数据范围

\(n\leq 10^5,Q\leq 10^5\) 且保证答案不会爆 \(longlong\)

\(Sol\)

设当前答案为 \(1\),但最优解不是 \(1\),将答案向最优解移动。

设移动的下一个节点的子树大小(子树中节点的点权和)为 \(x\),所有节点的点权和为 \(all\),当前节点与移动目标的连边边权为 \(d\),则 \(val\) 在原有基础上会减少 \(x\times d\),增加 \((all - x) \times d\),总的变化量为 \((all - 2\times x) \times d\)

不难发现,当 \(2\times x > all\) 时移动,\(val\) 的取值会更优。

同时,我们发现,设节点 \(i\) 的子树大小为 \(cnt_i\),所有 \(2\times cnt_i > all\) 的节点一定在一条以 \(1\) 为端点的链上。

不难反证,如果有两个节点同时满足上述条件,且不构成祖先后代关系,那两者的子树大小相加将大于 \(all\),显然不成立。

而我们的最优解将会是满足 \(cnt_i > all\) 中最深的点,这也不难想象,只要满足该条件,向下移动 \(val\) 一定更优。

最后的答案由两部分组成,其一是以 \(1\) 为答案时 \(val\) 的取值,为 \(\sum_{i = 1}^n dis(1, i)\times w_i\)

其二是移动后的偏移量,即 \(\sum_{1->ans} (all - 2\times cnt_i) \times d_i\) ,其中 \(d_i\) 为节点 \(i\) 与父节点连边的边权。

则 $$val = \sum_{i = 1}^n dis(1, i)\times w_i + all \times dis(1, ans) - 2\times \sum_{1->ans} d_i\times cnt_i$$

树链剖分加上线段树不难维护算式中的两段求和,确定 \(ans\) 节点可以通过线段树二分完成,具体地,维护一棵储存子树大小区间最大值的线段树(\(dfn\) 序),尽量向右,因为 \(dfn\) 序越大节点越深,且只可能有一条链,则一定能向右找到。

\(Code\)

#include <bits/stdc++.h>
#define int long long
using namespace std;
const int N = 1e5 + 10;
#define getchar() (p1 == p2 && (p2 = (p1 = buf) + fread (buf, 1, 1 << 21, stdin), p1 == p2) ? EOF : *p1++)
char buf[1 << 21], *p1 = buf, *p2 = buf;
inline int read()
{
  register int x = 0, f = 0;
  register char c = getchar();
  while (!isdigit(c)) { if (c == '-') f = 1;c = getchar(); }
  while (isdigit(c)) { x = (x << 3) + (x << 1) + c - 48;c = getchar(); }
  return f ? -x : x;
}
struct edge{ int v, w; };
int n, Q, tot, all, mul;
int up[N], pw[N], pot[N], dfn[N], siz[N], son[N], dep[N], top[N], dist[N];
vector<edge> G[N];
inline void DFS(int u, int fa)
{
	siz[u] = 1, up[u] = fa, dep[u] = dep[fa] + 1;
	for(register edge to : G[u]){
		if(to.v == fa) continue;
		dist[to.v] = dist[u] + to.w;
		DFS(to.v, u), siz[u] += siz[to.v];
		if(siz[son[u]] < siz[to.v]) son[u] = to.v; 
	}
}
inline void Pre(int u, int t) //树剖 
{
	top[u] = t, dfn[u] = ++tot, pot[tot] = u;
	pw[tot] = dist[u] - dist[up[u]];
	if(son[u]) Pre(son[u], t);
	for(register edge to : G[u]){
		if(to.v == up[u] || to.v == son[u]) continue;
		Pre(to.v, to.v);
	}
}
struct XDTree{
	#define lson k << 1
	#define rson k << 1 | 1
	#define mid ((l + r) >> 1)
	int cnt[N << 2], tag[N << 2], sum[N << 2], dis[N << 2];
	inline void build(int k, int l, int r){
		if(l == r) { dis[k] = pw[l]; return; }
		build(lson, l, mid), build(rson, mid + 1, r);
		dis[k] = dis[lson] + dis[rson];
	}
	inline void pushdown(int k){
		if(!tag[k]) return;
		tag[lson] += tag[k], tag[rson] += tag[k];
		sum[lson] += tag[k] * dis[lson], sum[rson] += tag[k] * dis[rson];
		cnt[lson] += tag[k], cnt[rson] += tag[k];
		tag[k] = 0;
	}
	inline void update(int k, int l, int r, int x, int y, int v){ //区间修改 
		if(l >= x && r <= y) { cnt[k] += v, sum[k] += v * dis[k], tag[k] += v; return; }
		pushdown(k);
		if(y <= mid) update(lson, l, mid, x, y, v);
		else if(x > mid) update(rson, mid + 1, r, x, y, v);
		else update(lson, l, mid, x, mid, v), update(rson, mid + 1, r, mid + 1, y, v);
		sum[k] = sum[lson] + sum[rson], cnt[k] = max(cnt[lson], cnt[rson]);
	}
	inline int query(int k, int l, int r, int x, int y){
		if(l >= x && r <= y) return sum[k];
		pushdown(k);
		if(y <= mid) return query(lson, l, mid, x, y);
		else if(x > mid) return query(rson, mid + 1, r, x, y);
		return query(lson, l, mid, x, mid) + query(rson, mid + 1, r, mid + 1, y);	
	}
}T;
inline void Add_data(int u, int cag)
{
	all += cag, mul += cag * dist[u];
	while(u) T.update(1, 1, n, dfn[top[u]], dfn[u], cag), u = up[top[u]];
}
inline int Sol(int u)
{
	int res = 0;
	while(u) res += T.query(1, 1, n, dfn[top[u]], dfn[u]), u = up[top[u]];
	return res;
}
inline int weight()
{
	int l = 1, r = n, k = 1;
	while(l < r){
		T.pushdown(k);
		if((T.cnt[rson] << 1) >= all) l = mid + 1, k = rson;
		else r = mid, k = lson;
	}
	return pot[l];
}
signed main()
{
	n = read(), Q = read();
	for(register int i = 1, x, y, z; i < n; i++)
		x = read(), y = read(), z = read(), G[x].push_back((edge){y, z}), G[y].push_back((edge){x, z});
	DFS(1, 0), Pre(1, 1), T.build(1, 1, n);
	while(Q--){
		int u = read(), cag = read(), pos;
		Add_data(u, cag), pos = weight();
		printf("%lld\n", mul + all * dist[pos] - 2 * Sol(pos));
	}
	return 0;
}
posted @ 2022-07-05 20:25  ╰⋛⋋⊱๑落叶๑⊰⋌⋚╯  阅读(76)  评论(0编辑  收藏  举报