[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;
}