【ZJOI2018】历史

mac2022-06-30  152

【ZJOI2018】历史

该来的总归还是会来的……

题意:

题目传送门

题解:

考虑把题意转化成一个更加科学一点的模型,发现这个崛起操作类似于\(LCT\)\(Access\)操作,继续分析一下,发现每次崛起的灾难度就是这次\(Access\)的轻重链的切换次数。那么题目就转化成了给出一个\(Access\)的顺序,让操作完之后的轻重链切换次数最大。

接下来考虑什么时候会造成轻重链的切换。显然,对于一个点\(x\),如果\(x\)所引出的重链发生了改变,那么说明来自\(x\)的两个不同子树中的节点进行了\(Access\)操作。

另一个容易发现的的结论就是每个点对于答案的贡献是独立的,所以我们可以单独计算每个点的贡献。我们记\(Ans_x\)表示\(x\)节点处轻重链切换次数的最大值,那么答案就是\(\sum_x Ans_x\)

接下来考虑如何计算一个点轻重链切换次数的最大值,我们记\(A_v\)表示\(v(v \in son_x)\)这个子树中的\(Access\)次数。我们实际上可以将问题的模型进行再次转化,对于一个点\(x\),我们把\(x\)子树中的所有\(Access\)操作看成一个小球,在\(x\)同一个孩子节点的子树下的球颜色一样,现在需要安排一个球的摆放顺序,使得相邻两个球颜色不同的数量最多。

考虑一种贪心构造的方法,我们记\(Mx_x\)表示\(x\)的孩子节点中\(A\)最大的值,如果\(Mx_x * 2 > A_x + 1\),那么答案就是\(2 * (A_x - Mx_x)\),否则答案就是\(A_x - 1\)。这个是容易证明的,我们尝试把其他颜色的球放在颜色最多的球的间隔中,如果放的满,那么此时其余颜色的球最少为\(Mx_x - 1\),答案就是球的个数减一,即\(A_x - 1\),否则说明一些颜色最多的球会放在一起,那么答案就是其余颜色球的个数乘以\(2\),即\(2 * (A_x - Mx_x)\)

于是我们现在就有了一个\(O(n)\)计算贡献的方法了。考虑如何优化。

对于一个点\(u\),如果某个点\(v\)\(A_v * 2 > A_u + 1\),那么我们就记\(u\)\(v\)的边为重边,否则为轻边,这样我们的答案可以简单的计算了,如果\(u\)没有重边,那么答案就是\(A_x - 1\),否则就是\(2 * (A_x - A_{heavyson})\)

我们发现,对于一次修改,影响到的就是\(x\)到根的一条链。对于经过的边,我们分情况讨论:

如果经过的边是重边,由于每次加上的一定是一个正整数,所以原本是重边的边仍然是重边,并且答案不变,因为\(A_x\)\(Mx_x\)都变大了\(w_i\)

如果经过的边是轻边,那么这次修改操作可能会让它变成重边,那么我们判断其是否会变成重边,然后重新计算一下答案即可。

为什么这样复杂度是正确的呢?考虑每经过一条轻边,\(A\)的值至少会乘以\(2\),那么轻边的个数最多为\(log\)值域 级别的。所以每次修改只会修改\(log\)次。

至于如何维护这棵树呢?一种方法是使用\(LCT\),稍微不同的是\(Access\)操作的时候不是将到根的所有边都设置成重边,而是按照上述的方法判断是否切换。然后复杂度分析仍然是按照\(LCT\)的分析,还是一样的。

Code:

#include <bits/stdc++.h> using namespace std; const int N = 4e5 + 50; typedef long long ll; int n, m, tot; ll a[N], res[N]; ll ans; int heav[N], head[N]; struct edge { int to, nxt; }E[N << 1]; namespace LCT { struct node { int ch[2]; int fa; ll sum, tag; }t[N << 1]; #define ls(o) t[o].ch[0] #define rs(o) t[o].ch[1] #define pa(o) t[o].fa int Which(int o) { return rs(pa(o)) == o; } int Isroot(int o) { return ls(pa(o)) != o && rs(pa(o)) != o; } void Apply(int o, ll v) { t[o].sum += v; t[o].tag += v; } void Down(int o) { if(!o || !t[o].tag) return ; if(ls(o)) Apply(ls(o), t[o].tag); if(rs(o)) Apply(rs(o), t[o].tag); t[o].tag = 0; } void Rotate(int o) { int f = pa(o), ff = pa(f), c = Which(o); if(!Isroot(f)) t[ff].ch[Which(f)] = o; t[o].fa = ff; pa(t[o].ch[c ^ 1]) = f; t[f].ch[c] = t[o].ch[c ^ 1]; t[o].ch[c ^ 1] = f; t[f].fa = o; } void TreeDown(int o) { if(!Isroot(o)) TreeDown(pa(o)); Down(o); } void Splay(int o) { TreeDown(o); for(; !Isroot(o); Rotate(o)) { if(!Isroot(pa(o))) Rotate( Which(o) ^ Which(pa(o)) ? o : pa(o)); } } int Findroot(int o) { while(ls(o)) Down(o), o = ls(o); return o; } void Getans(int o) { ans -= res[o]; res[o] = heav[o] ? (2ll * (t[o].sum - t[heav[o]].sum)) : (t[o].sum - 1); if(a[o] * 2 > t[o].sum + 1) res[o] = 2ll * (t[o].sum - a[o]); ans += res[o]; } void Access(int o, ll v) { a[o] += v; for(int y = 0; o; y = o, o = pa(o)) { Splay(o); t[o].sum += v; if(ls(o)) Apply(ls(o), v); if(heav[o]) { TreeDown(heav[o]); if(t[heav[o]].sum * 2 <= t[o].sum + 1) heav[o] = 0, rs(o) = 0; } int v = Findroot(y); if(t[v].sum * 2 > t[o].sum + 1) heav[o] = v, rs(o) = v; Getans(o); } } } using namespace LCT; void Addedge(int u, int v) { E[++tot].to = v; E[tot].nxt = head[u]; head[u] = tot; } void Dfs(int o, int fa) { t[o].fa = fa; t[o].sum = a[o]; ll Mx = -1, pos; for(int i = head[o]; ~i; i = E[i].nxt) { int to = E[i].to; if(to == fa) continue; Dfs(to, o); t[o].sum += t[to].sum; if(t[to].sum > Mx) { Mx = t[to].sum; pos = to; } } if(Mx * 2 > t[o].sum + 1) heav[o] = pos; Getans(o); rs(o) = heav[o]; } int main() { memset(head, -1, sizeof head); scanf("%d%d", &n, &m); for(int i = 1; i <= n; i++) scanf("%lld", &a[i]); for(int i = 1, u, v; i < n; i++) { scanf("%d%d", &u, &v); Addedge(u, v); Addedge(v, u); } Dfs(1, 0); printf("%lld\n", ans); for(int i = 1; i <= m; i++) { int x; ll y; scanf("%d%lld", &x, &y); Access(x, y); printf("%lld\n", ans); } return 0; }

转载于:https://www.cnblogs.com/Apocrypha/p/10732346.html

最新回复(0)