「ZJOI2018」历史
「ZJOI2018」历史
前置知识
\(\text{LCT}\) 维护子树信息,考虑辅助树上一个节点的子树信息只是其代表的这一段链的信息,设 \(S(u)\) 为节点 \(u\) 的子树信息,那么在辅助树上我们维护的是:
考虑它们的实际意义 \(lson\) 是 \(u\) 的父亲,\(rson\) 是 \(u\) 的重儿子,显然 \(S(lson)\) 是我们不需要的,而真正的辅助信息只算了节点本身和重儿子。
考虑按照这样算的话, \(u\) 的每一个轻儿子代表的子树信息都是合法的,那么可以再开一个 \(vs(u)\) 把所有轻儿子的 \(S\) 加上去。
轻重链切换的时候暴力更新一下 \(vs(u)\) 即可,动态加边的话因为要影响虚边,要把两个点都 \(\text{makeroot}\) 来保证更新的 \(vs(u)\) 合法,删边的话因为保证了关系是重儿子,所以直接做就好了,\(access\) 切换轻重边的时候需要重新维护一下。
但这种做法适用范围不大,信息不可减就做不了,只能单点不断条重链修改,然而对这道题来说已经足够了。
解题思路
观察发现每个节点的贡献是独立的,同一个儿子内的连续多次崛起不会贡献新的代价。显然要最大化每个节点的贡献需要尽可能两次崛起位于不同的儿子的子树。
设节点 \(u\) 的子树 \(a_i\) 和为 \(sz(u)\) ,\(mson=\max(\max_{u\rightarrow v}sz(v), a(u))\) 。
如果 \(2mson\leq sz(u)\) ,那么显然可以构造出操作序列满足不会有连续相同,贡献就是 \(sz(u)-1\)。
否则可以贪心的考虑,尽可能多用 \(mson\) ,节点 \(u\) 的贡献就是 \(2(sz(u)-mson)\)。
如果 \(u\) 的一个儿子 \(v\) 满足 \(2sz(v)>sz(u)\) ,那么我们记 \(u\rightarrow v\) 这条边为重边,其余边为轻边,类似于树链剖分和 \(\text{LCT}\) 的结构。
然后我们可以得到一些基本推论。
- 一个节点向下只会至多有一条重边。
- 一个节点到根的路径上只会至多有 \(log_{na_i}\) 条轻边,因为每有一条轻边 \(sz(u)\) 至少减少二分之一。
观察发现每一次修改操作,路径上的重边仍旧是重边,所以贡献不变,路径上的轻边可能会发生改变,只需要找到这些轻边暴力改就好了。
前面说了这个结构和 \(\text{LCT}\) 很类似,只需要每条重链用辅助树维护,支持单点加求一个点在根为 \(1\) 时的子树和,以及找一个点到一条路径上的轻边。
前面两个要求 \(\text{LCT}\) 维护子树信息可以直接做,查询只需要类似 \(access\) 走一遍,但是不要强制断开原有的重边,而是对于每条轻边按照前置知识的方法更新子树和再判断,细节有点多。
复杂度的话,单次修改 \(\text{splay}\) 重链条数次,轻边条数是 \(O(log_{na_i})\) ,和正常的 \(\text{LCT}\) 没啥区别,套用那个的均摊分析就是 \(\mathcal O(nlogn)\) 。
code
/*program by mangoyang*/
#include<bits/stdc++.h>
#define inf (0x7f7f7f7f)
#define Max(a, b) ((a) > (b) ? (a) : (b))
#define Min(a, b) ((a) < (b) ? (a) : (b))
typedef long long ll;
using namespace std;
template <class T>
inline void read(T &x){
int ch = 0, f = 0; x = 0;
for(; !isdigit(ch); ch = getchar()) if(ch == '-') f = 1;
for(; isdigit(ch); ch = getchar()) x = x * 10 + ch - 48;
if(f) x = -x;
}
const int N = 800005;
vector<int> g[N];
int ch[N][2], fa[N], n, m;
ll vs[N], a[N], s[N], ans;
#define son(x) (ch[fa[x]][1] == x)
#define isr(x) (ch[fa[x]][0] != x && ch[fa[x]][1] != x)
inline void update(int u){ s[u] = s[ch[u][0]] + s[ch[u][1]] + a[u] + vs[u]; }
inline void rotate(int x){
int F = fa[x], G = fa[F], w = son(x);
if(!isr(F)) ch[G][son(F)] = x; fa[x] = G;
ch[F][w] = ch[x][w^1], fa[ch[x][w^1]] = F;
ch[x][w^1] = F, fa[F] = x, update(F);
}
inline void splay(int x){
for(int F = fa[x]; !isr(x); rotate(x), F = fa[x])
if(!isr(F)) rotate(son(F) == son(x) ? F : x);
update(x);
}
inline ll calc(int x, ll t, ll h){
return ch[x][1] ? ((t - h) << 1) : ((a[x] << 1) > t ? ((t - a[x]) << 1) : t - 1);
}
inline void access(int x, int w){
splay(x);
ll now = s[x] - s[ch[x][0]], mson = s[ch[x][1]];
ans -= calc(x, now, mson), s[x] += w, a[x] += w, now += w;
if((mson << 1) <= now) vs[x] += mson, ch[x][1] = mson = 0;
ans += calc(x, now, mson), update(x);
int c = x; x = fa[x];
for(; x; c = x, x = fa[x]){
splay(x);
ll now = s[x] - s[ch[x][0]], mson = s[ch[x][1]];
ans -= calc(x, now, mson), s[x] += w, vs[x] += w, now += w;
if((mson << 1) <= now) vs[x] += mson, ch[x][1] = mson = 0;
if((s[c] << 1) > now) vs[x] -= (mson = s[c]), ch[x][1] = c;
ans += calc(x, now, mson), update(x);
}
}
inline void dfs(int u){
s[u] = a[u];
for(int i = 0; i < (int) g[u].size(); i++)
if(g[u][i] != fa[u]) fa[g[u][i]] = u, dfs(g[u][i]), s[u] += s[g[u][i]];
for(int i = 0; i < (int) g[u].size(); i++)
if(g[u][i] != fa[u] && s[g[u][i]] * 2 > s[u]) ch[u][1] = g[u][i];
vs[u] = s[u] - a[u] - s[ch[u][1]];
ans += calc(u, s[u], s[ch[u][1]]);
}
int main(){
read(n), read(m);
for(int i = 1; i <= n; i++) read(a[i]);
for(int i = 1, x, y; i < n; i++){
read(x), read(y);
g[x].push_back(y), g[y].push_back(x);
}
dfs(1);
printf("%lld\n", ans);
for(int i = 1, x, y; i <= m; i++){
read(x), read(y), access(x, y);
printf("%lld\n", ans);
}
return 0;
}