P4338:「ZJOI2018」历史 – 题解

主要思路

ZJOI 的题质量都好高啊!真的都是很棒的题。

其实这个覆盖的东西就相当于在 LCT 上进行 Access 时轻重链切换的次数。我们考虑静态的问题,对于点 \(u\) 来说,如果子树内有多个来自不同儿子子树内的子代节点,那么就需要进行合理的分配来把这个轻重链切换次数搞到最大。

转换一下就是两两不同颜色的点进行配对的最大值。设 \(S_0 = a_u, S_i = [\text{第 } i \text{个子树内的 access 次数之和}]\)。那么答案其实就是 \(\max\{ \sum_{i = 0}^m S_i – 1, 2(\sum_{i = 0}^m S_i – \max_{i \in [0, m]} S_i) \}\),前者是啥都不考虑,后者是连接到一个子节点。后者成为最大值的条件其实就是要成为重儿子。我们考虑维护这个东西。

发现其实我们用贪心的分配方案进行轻重链剖分时,复杂度也是对的。因为被贪心选择的那个子节点确实也是个重儿子,根据树链剖分那一套理论这个复杂度是成立的。

当我们进行修改时,我们就需要用 Access 操作来进行判断和修改。我们需要判断是否存在虚子树中的点成为重儿子,然后进行切换。

代码

// P4338.cpp
#include <bits/stdc++.h>

using namespace std;

const int MAX_N = 4e5 + 200;

typedef long long ll;

int n, q, head[MAX_N], current, ch[MAX_N][2], fa[MAX_N];
ll sum[MAX_N], val[MAX_N], vsum[MAX_N], ans;

struct edge
{
    int to, nxt;
} edges[MAX_N << 1];

#define lson (ch[p][0])
#define rson (ch[p][1])
#define mid ((l + r) >> 1)

void addpath(int src, int dst)
{
    edges[current].to = dst, edges[current].nxt = head[src];
    head[src] = current++;
}

int check(int p) { return ch[fa[p]][1] == p; }

bool isRoot(int p) { return ch[fa[p]][0] != p && ch[fa[p]][1] != p; }

void pushup(int p) { sum[p] = sum[lson] + sum[rson] + val[p] + vsum[p]; }

void rotate(int x)
{
    int y = fa[x], z = fa[y], dir = check(x), w = ch[x][dir ^ 1];
    fa[x] = z;
    if (!isRoot(y))
        ch[z][check(y)] = x;
    fa[y] = x, ch[x][dir ^ 1] = y;
    fa[w] = y, ch[y][dir] = w;
    pushup(y), pushup(x);
}

void splay(int p)
{
    for (int fat = fa[p]; fat = fa[p], !isRoot(p); rotate(p))
        if (!isRoot(fat))
            rotate(check(p) == check(fat) ? fat : p);
    pushup(p);
}

ll calc(int p, ll subtree, ll succ) { return rson ? (subtree - succ) * 2 : (val[p] * 2 > subtree ? (subtree - val[p]) * 2 : subtree - 1); }

void modify(int p, int delta)
{
    splay(p);
    ll subtree = sum[p] - sum[lson], succ = sum[rson];
    ans -= calc(p, subtree, succ), sum[p] += delta, subtree += delta, val[p] += delta;
    if (succ * 2 < subtree + 1)
        vsum[p] += succ, rson = 0;
    ans += calc(p, subtree, succ), pushup(p);
    int pre = p;
    p = fa[p];
    for (; p; pre = p, p = fa[p])
    {
        splay(p), subtree = sum[p] - sum[lson], succ = sum[rson];
        ans -= calc(p, subtree, succ), sum[p] += delta, vsum[p] += delta, subtree += delta;
        if (succ * 2 < subtree + 1)
            vsum[p] += succ, rson = 0, succ = 0;
        if (sum[pre] * 2 > subtree)
            vsum[p] -= sum[pre], rson = pre, succ = sum[pre];
        ans += calc(p, subtree, succ), pushup(p);
    }
}

void dfs(int u, int up)
{
    sum[u] = val[u];
    ll mx = val[u], p = 0;
    for (int i = head[u]; i != -1; i = edges[i].nxt)
        if (edges[i].to != up)
        {
            fa[edges[i].to] = u, dfs(edges[i].to, u), sum[u] += sum[edges[i].to];
            if (mx < sum[edges[i].to])
                mx = sum[edges[i].to], p = edges[i].to;
        }
    ans += min(sum[u] - 1, 2LL * (sum[u] - mx));
    if (sum[p] * 2 >= sum[u] + 1)
        ch[u][1] = p;
    vsum[u] = sum[u] - val[u] - sum[ch[u][1]];
}

int main()
{
    memset(head, -1, sizeof(head));
    scanf("%d%d", &n, &q);
    for (int i = 1; i <= n; i++)
        scanf("%lld", &val[i]);
    for (int i = 1, u, v; i <= n - 1; i++)
        scanf("%d%d", &u, &v), addpath(u, v), addpath(v, u);
    dfs(1, 0), printf("%lld\n", ans);
    while (q--)
    {
        int u, x;
        scanf("%d%d", &u, &x), modify(u, x), printf("%lld\n", ans);
    }
    return 0;
}

Leave a Reply

Your email address will not be published. Required fields are marked *