3924: [Zjoi2015]幻想乡战略游戏
Time Limit: 100 Sec Memory Limit: 256 MBSubmit: 1034 Solved: 478
[ Submit][ Status][ Discuss]
Description
傲娇少女幽香正在玩一个非常有趣的战略类游戏,本来这个游戏的地图其实还不算太大,幽香还能管得过来,但是不知道为什么现在的网游厂商把游戏的地图越做越大,以至于幽香一眼根本看不过来,更别说和别人打仗了。 在打仗之前,幽香现在面临一个非常基本的管理问题需要解决。 整个地图是一个树结构,一共有n块空地,这些空地被n-1条带权边连接起来,使得每两个点之间有一条唯一的路径将它们连接起来。在游戏中,幽香可能在空地上增加或者减少一些军队。同时,幽香可以在一个空地上放置一个补给站。 如果补给站在点u上,并且空地v上有dv个单位的军队,那么幽香每天就要花费dv×dist(u,v)的金钱来补给这些军队。由于幽香需要补给所有的军队,因此幽香总共就要花费为Sigma(Dv*dist(u,v),其中1<=V<=N)的代价。其中dist(u,v)表示u个v在树上的距离(唯一路径的权和)。 因为游戏的规定,幽香只能选择一个空地作为补给站。在游戏的过程中,幽香可能会在某些空地上制造一些军队,也可能会减少某些空地上的军队,进行了这样的操作以后,出于经济上的考虑,幽香往往可以移动他的补给站从而省一些钱。但是由于这个游戏的地图是在太大了,幽香无法轻易的进行最优的安排,你能帮帮她吗? 你可以假定一开始所有空地上都没有军队。
Input
Output
对于幽香的每个操作,输出操作完成以后,每天的最小花费,也即如果幽香选择最优的补给点进行补给时的花费。
Sample Input
1 2 1
2 3 1
2 4 1
1 5 1
2 6 1
2 7 1
5 8 1
7 9 1
1 10 1
3 1
2 1
8 1
3 1
4 1
Sample Output
1
4
5
6
HINT
Source
这道题我就呵呵了, 坑我一晚上 + 一下午. 昨天晚上状态极不好边想这道题边去颓课件去了, 导致效率极低. 最后集中想了20min没想出来选择看题解, 发现题解清一色的抛结论... 于是自己证了一下, 由于有个sigma写错了导致我推了1年... 后面才发现是傻逼结论. 然而一个晚上就这么在颓废中过去了QAQ... 不过网上都用的点分治|树链剖分 + 点分治. 昨晚回寝室睡觉时想到了一个纯树链剖分的做法. 于是今天下午来实现... 40min写完没想到被坑了一下午.
然而这道题是ZJOI2015 Day1 T1. 我选择go die. 讲真t3比t1简单啊(可能是因为我没往性质结论上想...
这道题要求的东西实际上就是带权重心. 为什么呢? 我们设sum[u]为u子树的d之和(d详情请看题目定义). 那么如果u的儿子v, sum[v] * 2 >= sum[root]的话那么最优点一定在v的子树里. 证明: 我们考虑以u作为补给点的花费设为p, 那么我们会发现若以v作为补给点的话花费是 p + (sum[root] - sum[v] * 2) * w[u, v]. 因为走过(u, v)这条边会使除了v子树的其他点i的所有花费+d[i] * w(u, v), 那么就是(sum[root] - sum[v]) * w(u, v). 然后v子树里的所有点的花费会 - sum[v] * w(u, v). 加起来就是上式辣. 那么如果sum[v] * 2 >= sum[root]的话花费就会减少. 而且这样的v由于sum[v]>=sum[u]/2所以只会最多有一个. 为什么不在其他子树里呢? 因为其他子树譬如v1满足sum[v1] < sum[root]/2那么v1子树里的点sum更小显然都满足, 那么花费只会增加不会减少. 所以我们只需要顺着根往下判断来找这个最优点就行了(直到儿子都比自己劣那此时就是最优点).
然后我们会发现会tle. 考虑怎么优化. 首先从根到最优点的路径是唯一确定的, 并且显然这条路径是垂直的链, 也就是说深度单增且dfs序单增. 并且从u要走到v的话肯定满足v是u中sum最大的. 那么我们用线段树维护dfs序区间max就可以啦. 每次判断即可(这个很好理解可以结合代码seg_query函数). 对于修改直接修改一条链的sum即可. 显然这可以用树链剖分来维护. 那怎么算答案呢? 我们知道每次移动从u到v花费差距是 (sum[root] - sum[v] * 2) * w[u, v]. 那么最优点与根的差距就是sum[root] * dis[v](到根距离) - 2 * (sum[v] * w(fa[v], v). 根的答案很好维护, 式子前面一项也很好维护. 第二项发现只跟v有关, 且只有sum一个在变. 那么树剖的时候顺带维护一下就好了. 因为每次修改链上都是区间加减, 对于一段dfs序区间的sum[v] * w(fa[v], v)之和(v属于这段区间)也就是加上了这段区间每个v到各自父亲的距离 * 当前改动的值. 详见代码(非常easy辣).
然而我老是wa的原因就是之前推导的时候没考虑边权, 直接用的dep... 然而样例恰好边权都是1, 我...
本来以为A了可以艹榜的, 发现只能在第二页(我的ID是lkq的, 我没有权限号...)... 我还是太naive了. 改天会了zkw线段树再回来艹一发.
话说为什么加了const &和fread读优还没有没加快啊. 而且Claris神犇什么鬼畜压行怎么又写了树剖+线段树还写了点分治都只有80多行啊
#include<bits/stdc++.h>
using namespace std;
typedef long long lnt;
const int maxn = 2e5 + 5;
lnt ans, all;
int n, Q, num, idx;
int siz[maxn], fa[maxn], son[maxn], seq[maxn], in[maxn], h[maxn], top[maxn], dis[maxn], dep[maxn], par[maxn];
inline const int read() {
register int x = 0, f = 1;
register char ch = getchar();
while (ch < '0' || ch > '9') {if (ch == '-') f = -1; ch = getchar();}
while (ch >= '0' && ch <= '9') x = (x << 3) + (x << 1) + ch - '0', ch = getchar();
return f * x;
}
struct edge {
int nxt, v, w;
}e[maxn << 1];
inline void add(int u, int v, int w) {
e[++ num].v = v, e[num].nxt = h[u], e[num].w = w, h[u] = num;
e[++ num].v = u, e[num].nxt = h[v], e[num].w = w, h[v] = num;
}
void dfs1(int u, int f) {
siz[u] = 1, fa[u] = f;
for (int i = h[u]; i; i = e[i].nxt) {
int v = e[i].v;
if (v == f) continue;
par[v] = e[i].w;
dep[v] = dep[u] + 1;
dis[v] = dis[u] + e[i].w;
dfs1(v, u);
siz[u] += siz[v];
if (siz[son[u]] < siz[v]) son[u] = v;
}
}
void dfs2(int u, int tp) {
top[u] = tp;
in[u] = ++ idx, seq[idx] = u;
if (son[u]) dfs2(son[u], tp);
for (int i = h[u]; i; i = e[i].nxt) {
int v = e[i].v;
if (v == fa[u] || v == son[u]) continue;
dfs2(v, v);
}
}
struct node {
node *ls, *rs;
int mid; lnt sum, flag, cmax, dsm;
inline void pushdown(int lf, int rg) {
if (flag) {
mid = (lf + rg) >> 1;
ls -> sum += flag * ls -> dsm;
rs -> sum += flag * rs -> dsm;
ls -> cmax += flag, rs -> cmax += flag;
ls -> flag += flag, rs -> flag += flag;
flag = 0;
}
}
inline void update() {
sum = ls -> sum + rs -> sum;
cmax = (ls -> cmax > rs -> cmax) ? ls -> cmax : rs -> cmax;
}
}pool[maxn << 1], *root, *tail = pool;
node* build(int lf, int rg) {
node* bt = ++ tail;
if (lf == rg) {
bt -> dsm = par[seq[lf]];
bt -> cmax = bt -> sum = 0;
return bt;
}
int mid = (lf + rg) >> 1;
bt -> ls = build(lf , mid), bt -> rs = build(mid + 1, rg);
bt -> update();
bt -> dsm = bt -> ls -> dsm + bt -> rs -> dsm;
return bt;
}
void modify(node* bt, int lf, int rg, int L, int R, int val) {
if (L <= lf && rg <= R) {
bt -> cmax += val;
bt -> flag += val;
bt -> sum += bt -> dsm * val;
return;
}
bt -> pushdown(lf, rg);
int mid = (lf + rg) >> 1;
if (L <= mid) modify(bt -> ls, lf, mid, L, R, val);
if (R > mid) modify(bt -> rs, mid + 1, rg, L, R, val);
bt -> update();
}
lnt query(node* bt, int lf, int rg, int L, int R) {
if (L <= lf && rg <= R) return bt -> sum;
bt -> pushdown(lf, rg);
int mid = (lf + rg) >> 1; lnt rt = 0;
if (L <= mid) rt += query(bt -> ls, lf, mid, L, R);
if (R > mid) rt += query(bt -> rs, mid + 1, rg, L, R);
bt -> update();
return rt;
}
int queryheart(node* bt, int lf, int rg) {
if (lf == rg) return seq[lf];
bt -> pushdown(lf, rg);
int mid = (lf + rg) >> 1, rt = 0;
if (all <= 2 * bt -> rs -> cmax) rt = queryheart(bt -> rs, mid + 1, rg);
else rt = queryheart(bt -> ls, lf, mid);
bt -> update();
return rt;
}
inline void seg_modify(int u, int val) {
while (u)
modify(root, 1, n, in[top[u]], in[u], val), u = fa[top[u]];
}
inline lnt seg_query() {
lnt rt = 0;
int u = queryheart(root, 1, n), ori = u;
while (u)
rt += query(root, 1, n, in[top[u]], in[u]), u = fa[top[u]];
return all * dis[ori] - 2 * rt;
}
int main() {
n = read(), Q = read();
register int i, u, v, w;
for (i = 1; i < n; ++ i)
u = read(), v = read(), w = read(), add(u, v, w);
dfs1(1, 0), dfs2(1, 1);
root = build(1, n);
for (i = 1; i <= Q; ++ i) {
u = read(), w = read();
seg_modify(u, w);
all += w, ans += 1ll * dis[u] * w;
lnt hh = (ans + seg_query());
printf("%lld\n", hh);
}
return 0;
}