P3345:「ZJOI2015」幻想乡战略游戏题解

主要解法

动态点分治真不友好。

我们先来考虑\(O(nq)\)的静态做法。我们可以随便定一个根,\(O(n)\)处理两个数组来记录子树的权值和与子树之外的权值和,然后再来一次\(O(n)\)判定答案是否最小。

其实从这个地方,我们可以发现一些关系:如果一个点的子节点比父节点更优,会有这样的关系:

\[ dist(fa[u], u) * (sum[fa[u]] – sum[u]) < dist(fa[u], u) * (sum[u]) \]

其中\(sum\)数组是子树的权值和。总结一下就是:

\[ 2 * sum[u] > sum[fa[u]] \]

当这个式子成立时,那么就意味着要把节点往子树挪了。挪入子树之后继续判断、换位置,直到状态稳定(也就是式子不成立的时候)。

我们现在知道了从任意一个点去找答案节点的判断条件了,现在我们要尽量把找答案的复杂度降到\(\log n\)级别,要不然铁定 TLE。如果,我们可以有一种方式,在修改答案的时候只经过级别为\(\log n\)的节点数,且,答案统计的时候也只经过级别为\(\log n\)的节点数,那么就不会 TLE 了。显然,这种方式就是动态点分治。动态点分治可以做到上述操作。

在动态点分治中,我们会建立一颗点分树,不同的重心通过二分的层次形成一棵树,这棵树的高度是\(O(\log n)\)级别的。所以,我们在修改答案的时候,只需要绕着这\(\log n\)个节点向上跳,维护\(sum\)就行了,同时更新部分答案\(sumq\)。更新部分答案的时候要找准连接两个点分治重心的那条边,这样就可以得到距离来做系数了。回答询问时,注意将答案分成两块:子树和子树之外的点。

// FOJ4037.cpp
#include <bits/stdc++.h>
#define ll long long

using namespace std;

const int MAX_N = 1e5 + 2000;

// standard data of graph;
int head[MAX_N], current, n, q, siz[MAX_N];
// LCA backend;
int euler_tot, firstOccurence[MAX_N], euler_order[MAX_N << 1], rmq[20][MAX_N << 1], dep[MAX_N], prefix[MAX_N];
// divide & conquer;
bool tag[MAX_N];
// root stuff;
int max_son[MAX_N], root, general_root;

struct edge
{
    int to, weight, nxt;
} edges[MAX_N << 1];
// dq tree stuff;
vector<edge> dqGraph[MAX_N];
int dq_fa[MAX_N];
ll di[MAX_N], sum[MAX_N], sumq[MAX_N];

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

void init_euler(int u, int fa)
{
    dep[u] = dep[fa] + 1;
    euler_order[firstOccurence[u] = ++euler_tot] = u;
    for (int i = head[u]; i != -1; i = edges[i].nxt)
        if (edges[i].to != fa)
        {
            prefix[edges[i].to] = prefix[u] + edges[i].weight;
            init_euler(edges[i].to, u), euler_order[++euler_tot] = u;
        }
}

void init_rmq()
{
    for (int i = 1; i <= euler_tot; i++)
        rmq[0][i] = euler_order[i];
    for (int i = 1; i < 20; i++)
        for (int j = 1; j <= euler_tot - (1 << i) + 1; j++)
        {
            int x = rmq[i - 1][j], y = rmq[i - 1][j + (1 << (i - 1))];
            rmq[i][j] = (dep[x] > dep[y]) ? y : x;
        }
}

int dist(int x, int y)
{
    int l = firstOccurence[x], r = firstOccurence[y];
    if (l > r)
        swap(l, r);
    int len = log2(r - l + 1), lca1 = rmq[len][l], lca2 = rmq[len][r - (1 << len) + 1];
    int lca = (dep[lca1] < dep[lca2]) ? lca1 : lca2;
    return prefix[x] + prefix[y] - (prefix[lca] << 1);
}

void updateSize(int u, int fa)
{
    siz[u] = 1;
    for (int i = head[u]; i != -1; i = edges[i].nxt)
        if (edges[i].to != fa && !tag[edges[i].to])
            updateSize(edges[i].to, u), siz[u] += siz[edges[i].to];
}

void __find_root(int u, int fa, int fake_root)
{
    max_son[u] = siz[fake_root] - siz[u];
    for (int i = head[u]; i != -1; i = edges[i].nxt)
        if (edges[i].to != fa && !tag[edges[i].to])
            max_son[u] = max(max_son[u], siz[edges[i].to]);
    if (max_son[u] < max_son[root])
        root = u;
    for (int i = head[u]; i != -1; i = edges[i].nxt)
        if (edges[i].to != fa && !tag[edges[i].to])
            __find_root(edges[i].to, u, fake_root);
}

void find_root(int u) { updateSize(u, 0), root = u, __find_root(u, 0, u); }

int build_dq_tree(int u, int fa)
{
    find_root(u);
    if (general_root == 0)
        general_root = root;
    tag[root] = true, dq_fa[root] = fa;
    int pt = root;
    for (int i = head[root]; i != -1; i = edges[i].nxt)
        if (!tag[edges[i].to])
        {
            int dest = build_dq_tree(edges[i].to, pt);
            dqGraph[pt].push_back(edge{dest, edges[i].to, -1});
        }
    return pt;
}

void update(int u, int delta)
{
    int curt = u;
    di[u] += delta;
    while (u)
    {
        sum[u] += delta;
        sumq[u] += 1LL * delta * dist(curt, dq_fa[u] ? dq_fa[u] : u);
        u = dq_fa[u];
    }
}

ll move_station(int src, int dst, int channel)
{
    ll ans = 0, cnt = di[src];
    for (int i = 0, siz = dqGraph[src].size(); i < siz; i++)
    {
        int v = dqGraph[src][i].to;
        if (v != dst)
            ans += sumq[v], cnt += sum[v];
    }
    di[channel] += cnt;
    return ans + cnt * dist(src, channel);
}

ll query(ll ans, int u)
{
    for (int i = 0, siz = dqGraph[u].size(); i < siz; i++)
    {
        int v = dqGraph[u][i].to;
        if ((sum[v] << 1) > sum[u])
        {
            // consider to move the station;
            ll channel = dqGraph[u][i].weight, pre = di[channel], tmp, delta = move_station(u, v, channel);
            tmp = di[channel] - pre;
            for (int pt = channel; pt != u; pt = dq_fa[pt])
                sum[pt] += tmp, sumq[pt] += tmp * dist(channel, dq_fa[pt] ? dq_fa[pt] : pt);
            ll nans = 0;
            for (int j = 0, siz2 = dqGraph[v].size(), dst; j < siz2; j++)
                dst = dqGraph[v][j].to, nans += sumq[dst];
            ans = delta + query(nans, v);
            for (int pt = channel; pt != u; pt = dq_fa[pt])
                sum[pt] -= tmp, sumq[pt] -= tmp * dist(channel, dq_fa[pt] ? dq_fa[pt] : pt);
            di[channel] = pre;
            return ans;
        }
    }
    return ans;
}

int main()
{
    // graph construction;
    memset(head, -1, sizeof(head));
    scanf("%d%d", &n, &q);
    for (int i = 1, u, v, w; i <= n - 1; i++)
        scanf("%d%d%d", &u, &v, &w), addpath(u, v, w), addpath(v, u, w);
    // initialization;
    init_euler(1, 0), init_rmq(), build_dq_tree(1, 0);
    while (q--)
    {
        int x, y;
        scanf("%d%d", &x, &y), update(x, y);
        printf("%lld\n", query(sumq[general_root], general_root));
    }
    return 0;
}

Leave a Reply

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