BZOJ3924: [Zjoi2015]幻想乡战略游戏(动态树分治)

传送门
题意:
给一棵树,每次给一些点增加点权,求一个点,使得 jidis(i,j)val[j] 最小,输出最小值。

题解:动态树分治

官方题解已经讲得很清楚了:

首先我们假设每次操作过后我们可以快速地在线查询以任意一个点为关键点得到的权值和,那么在这种情况下如何求出最小权值?
为了表达方便,我们不妨设当前以点u为关键点求得的权值和为 Su ,那么我们不难发现这样一个性质:在树上任意两点a, b之间的路径上, Su 构成了一个存在极小值的单峰函数。证明也很简单:考虑路径上任意一条边e,设e两端点分别为s, t,两端连接的点集分别为S, T,边权为e.v。则关键点从s走到t的过程中权值和的变化量:

Δ=StSs=(uSvaluvTvalv)e.v.
而在转移的过程中,点t和它的不在链上的后代结点都将从T集合转移到S集合,即 (uSvaluvTvalv) 是单调递增的,又由题意得知边权都是正整数,因此函数 Δ=StSs=(tSvaluvTvalv)e.v. 的零点区间一定唯一(由于 Δ 是离散函数,这里“零点区间”指的是左右两侧函数值正负性相反的区间),且左负右正。由于 Δ 表示的是S函数的增量,那么 Δ 的零点区间唯一且左负右正就证明了S是存在极小值的单峰函数。
那么我们设点c为我们要求的一个带权重心。考虑树上任意一点u和它到c之间的路径,由于u的S函数取最小值,又由路径上S函数值的单峰性,我们可以证明在从u到c的路径上S值是单调递增的,而相邻两点S值相同当且仅当这两点的S值均为 Su ,即最小值。最后这点结论可以由“零点区间连续”自然地得出。
有了这条性质,查询最小权值就好办了。我们可以在树上任取一点u将树有根化,判断它的各邻接点的S值是否小于 Su 。若存在一点v使得 Sv<Su ,那么根据上面的结论,我们知道答案一定在v所在的子树中,递归查询即可。若不存在这样的点v,那么答案一定是 Su
听起来很爽对不对?然而,如果我们每次在树上“任取一点”,最坏情况下递归的深度可以达到 O(N) 级别,时间复杂度退化得很严重。怎么办呢?我们可以在树上找到一点u,使得以u为根最大的子树的规模最小化(一般称u为这棵树的重心)。那么这样每棵子树的规模都不会超过原树规模的1/2,那么不难证明此时递归查询的深度就成了 O(logN)
再来考虑开头我们假设的我们已经会了的操作——在线查询任意一个 Su
考虑我们刚才建立的重心分治结构。对点v进行修改时,我们可以花费 O(logN) 的时间更新v所在的每一层分治结构的重心维护的答案(即在分治u维护的答案中增加 dist(u,v)Δvalv ),并记录每层分治结构中的结点对上一层分治维护的答案的贡献。在对点v查询时,先将答案设为v分治中维护的答案,然后向上移动累加答案:在从分治current向它的上一层分治parent移动时,在parent维护的答案中减去current对它的贡献得到 δS ,将得到的结果临时当做点v的后代累加进答案。即 Ans=lastAns+δS+(SumparentSumcurrent)dist(parent,v) ,其中 Sumt 表示t维护的分治结构中所有点权的平凡加和。这样,我们就会做这道题了。
如果我们用倍增LCA法求dist,时间复杂度为 O((N+M)log3N) ,可能有些卡常数。考虑到操作不会改变原树的结构,我们可以在 O(NlogN) 的时间内预处理后通过ST表维护DFS序列来求LCA,总时间复杂度 O((N+M)log2N) .

贴一份 写得很丑的代码:

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
namespace IO
{
streambuf *ib, *ob;
int buf[50];
inline void init()
{
    ios::sync_with_stdio(false);
    cin.tie(NULL);
    cout.tie(NULL);
    ib = cin.rdbuf();
    ob = cout.rdbuf();
}

inline int read()
{
    char ch = ib->sbumpc();
    int i = 0, f = 1;
    while (!isdigit(ch))
    {
        if (ch == '-')
            f = -1;
        ch = ib->sbumpc();
    }
    while (isdigit(ch))
    {
        i = (i << 1) + (i << 3) + ch - '0';
        ch = ib->sbumpc();
    }
    return i * f;
}

inline void W(ll x)
{
    if (!x)
    {
        ob->sputc('0');
        return;
    }
    if (x < 0)
    {
        ob->sputc('-');
        x = -x;
    }
    while (x)
        buf[++buf[0]] = x % 10, x /= 10;
    while (buf[0])
        ob->sputc(buf[buf[0]--] + '0');
}
}
using namespace IO;
const int Maxn = 2e5 + 50;
int n, m, G, vis[Maxn], sze[Maxn], total, mx, rt;
int mn[Maxn][20], ind, pos[Maxn], id[Maxn], cnt, PosInMn[Maxn], vt = 1;
int Log[Maxn * 2];
typedef pair<int, ll> pil;
typedef pair<int, int> pii;
ll dis[Maxn], ValToFa[Maxn], tot, sum[Maxn], val[Maxn];
vector<pii> edge[Maxn];
struct Graph
{
    int fa[Maxn];
    vector<pil> edge[Maxn];
} g1, g2;
inline int getlca(int x, int y)
{
    if (x == y)
        return x;
    int pos1 = PosInMn[x], pos2 = PosInMn[y];
    if (pos1 > pos2)
        swap(pos1, pos2);
    return pos[min(mn[pos1][Log[pos2 - pos1]], mn[pos2 - (1 << Log[pos2 - pos1]) + 1][Log[pos2 - pos1]])];
}
inline ll getdis(int x, int y)
{
    return dis[x] + dis[y] - 2ll * dis[getlca(x, y)];
}
inline void getG(int now, int fa)
{
    sze[now] = 1;
    int mxnow = 0;
    for (int e = g1.edge[now].size() - 1; e >= 0; e--)
    {
        int v = g1.edge[now][e].first;
        if (v == fa || vis[v])
            continue;
        getG(v, now);
        sze[now] += sze[v];
        mxnow = max(mxnow, sze[v]);
    }
    mxnow = max(mxnow, total - sze[now]);
    if (mxnow < mx)
        G = now, mx = mxnow;
}

inline void calc(int now, int f)
{
    sze[now] = 1;
    for (int e = g1.edge[now].size() - 1; e >= 0; e--)
    {
        int v = g1.edge[now][e].first;
        if (v == f || vis[v])
            continue;
        calc(v, now);
        sze[now] += sze[v];
    }
}

inline void work(int now)
{
    vis[now] = 1;
    for (int e = g1.edge[now].size() - 1; e >= 0; e--)
    {
        int v = g1.edge[now][e].first;
        if (vis[v])
            continue;
        calc(v, now);
        total = mx = sze[v];
        getG(v, now);
        g2.edge[now].push_back(make_pair(G, getdis(now, G)));
        g2.fa[G] = now;
        edge[now].push_back(make_pair(v, G));
        work(G);
    }
}

inline void SplitTree()
{
    total = mx = n;
    getG(1, 0);
    rt = G;
    work(G);
}

inline void dfs(int now, int f = 0, int dist = 0)
{
    g1.fa[now] = f;
    dis[now] = dis[f] + dist;
    pos[id[now] = ++ind] = now;
    mn[++cnt][0] = id[now];
    PosInMn[now] = cnt;
    for (int e = g1.edge[now].size() - 1; e >= 0; e--)
    {
        int v = g1.edge[now][e].first;
        if (v == f)
            continue;
        dfs(v, now, g1.edge[now][e].second);
        mn[++cnt][0] = id[now];
    }
}
inline void modify(int now, int ori, ll v)
{
    sum[now] += v;
    if (g2.fa[now])
    {
        ll t = getdis(ori, g2.fa[now]) * v;
        ValToFa[now] += t;
        val[g2.fa[now]] += t;
        modify(g2.fa[now], ori, v);
    }
}
inline ll calcval(int x, int ori)
{
    ll res = val[x];
    while (g2.fa[x])
    {
        res += (val[g2.fa[x]] - ValToFa[x] + getdis(ori, g2.fa[x]) * (sum[g2.fa[x]] - sum[x]));
        x = g2.fa[x];
    }
    return res;
}
inline ll query(int now)
{
    ll tnow = calcval(now, now);
    vis[now] = vt;
    for (int e = edge[now].size() - 1; e >= 0; e--)
    {
        int v = edge[now][e].first;
        if (calcval(v, v) <= tnow)
        {
            return query(edge[now][e].second);
        }
    }
    return tnow;
}
int main()
{
    init();
    n = read(), m = read();
    for (int i = 1; i < n; i++)
    {
        int x = read(), y = read(), z = read();
        g1.edge[x].push_back(make_pair(y, z));
        g1.edge[y].push_back(make_pair(x, z));
    }
    dfs(1, 0);
    Log[1] = 0;
    for (int i = 2; i <= 2 * n; i++)
        Log[i] = Log[i >> 1] + 1;
    for (int i = 1; i <= Log[2 * n]; i++)
        for (int j = 1; j + (1 << i) - 1 <= cnt; j++)
            mn[j][i] = min(mn[j][i - 1], mn[j + (1 << (i - 1))][i - 1]);
    SplitTree();
    while (m--)
    {
        int x = read(), y = read();
        tot += y;
        ++vt;
        modify(x, x, y);
        W(query(rt));
        ob->sputc('\n');
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值