P3320 [SDOI2015]寻宝游戏 题解

一道虚树题,但是做完之后发现跟虚树半点关系没有,反而更像思维题。

首先默认读者会虚树,然后会发现这道题有一个朴素做法就是对于每一次操作完之后的点我们都建一棵虚树,然后这个虚树上面的 DP 是简单的。

然后这个复杂度最坏是 O ( n m ) O(nm) O(nm) 的,显然会炸,于是我们要想想办法。

发现实际上我们没有必要做一遍 DP,在我们求出了原树的 dfs 序之后其实这玩意就很好搞了。

接下来以加入一个点为例,我们将其丢进目前关键点序列里面之后发现实际上这个点只会影响到 dfs 序在这个点之前的和在这个点之后的,就像这样:

在这里插入图片描述

如图, x , y x,y x,y 是两个 dfs 序与 z z z 最接近的点,然后发现只需要将红色路径改成蓝色路径就可以了,而且可以证明这个方式一定是对答案影响最小的。

综上我们根本没必要建虚树,只需要用个 set 维护关键点序列就好了,弄个倍增或者树上差分之类的求一下路径距离即可。

GitHub:CodeBase-of-Plozia

Code:

/*
========= Plozia =========
    Author:Plozia
    Problem:P3320 [SDOI2015]寻宝游戏
    Date:2021/12/14
========= Plozia =========
*/

#include <bits/stdc++.h>
using std::set;

typedef long long LL;
const int MAXN = 1e5 + 5;
int n, q, Head[MAXN], cnt_Edge = 1, fa[MAXN][21], dep[MAXN], dfn[MAXN], ys[MAXN];
LL g[MAXN][21], ans;
bool book[MAXN];
struct node { int To, val, Next; } Edge[MAXN << 1];
set <int> s;
set <int> :: iterator it1, it2, it3;

int Read()
{
    int sum = 0, fh = 1; char ch = getchar();
    for (; ch < '0' || ch > '9'; ch = getchar()) fh -= (ch == '-') << 1;
    for (; ch >= '0' && ch <= '9'; ch = getchar()) sum = sum * 10 + (ch ^ 48);
    return sum * fh;
}
int Max(int fir, int sec) { return (fir > sec) ? fir : sec; }
int Min(int fir, int sec) { return (fir < sec) ? fir : sec; }
void add_Edge(int x, int y, int z) { ++cnt_Edge; Edge[cnt_Edge] = (node){y, z, Head[x]}; Head[x] = cnt_Edge; }

void dfs(int now, int father)
{
    fa[now][0] = father; dep[now] = dep[father] + 1; dfn[now] = ++dfn[0]; ys[dfn[0]] = now;
    for (int i = Head[now]; i; i = Edge[i].Next)
    {
        int u = Edge[i].To;
        if (u == father) continue ;
        g[u][0] = Edge[i].val; dfs(u, now);
    }
}

void init()
{
    for (int j = 1; j <= 20; ++j)
        for (int i = 1; i <= n; ++i)
        {
            fa[i][j] = fa[fa[i][j - 1]][j - 1];
            g[i][j] = g[i][j - 1] + g[fa[i][j - 1]][j - 1];
        }
}

LL Ask(int x, int y)
{
    LL val = 0;
    if (dep[x] < dep[y]) std::swap(x, y);
    for (int i = 20; i >= 0; --i)
        if (dep[fa[x][i]] >= dep[y]) { val += g[x][i]; x = fa[x][i]; }
    if (x == y) return val;
    for (int i = 20; i >= 0; --i)
        if (fa[x][i] != fa[y][i]) { val += g[x][i] + g[y][i]; x = fa[x][i]; y = fa[y][i]; }
    return val + g[x][0] + g[y][0];
}

int main()
{
    n = Read(), q = Read();
    for (int i = 1; i < n; ++i)
    {
        int x = Read(), y = Read(), z = Read();
        add_Edge(x, y, z); add_Edge(y, x, z);
    }
    dfs(1, 1); init();
    for (int i = 1; i <= q; ++i)
    {
        int x = Read();
        if (book[x])
        {
            book[x] = 0; it1 = s.lower_bound(dfn[x]); it2 = s.upper_bound(dfn[x]);
            it3 = s.end(); int p1, p2;
            if (it1 != s.begin()) p1 = *(--it1); else p1 = *(--it3);
            if (it2 != s.end()) p2 = *it2; else p2 = *s.begin();
            ans = ans - Ask(ys[p1], x) - Ask(ys[p2], x) + Ask(ys[p1], ys[p2]);
            printf("%lld\n", ans); s.erase(dfn[x]);
        }
        else
        {
            s.insert(dfn[x]); it1 = s.lower_bound(dfn[x]); it2 = s.upper_bound(dfn[x]);
            book[x] = 1; it3 = s.end(); int p1, p2;
            if (it1 != s.begin()) p1 = *(--it1); else p1 = *(--it3);
            if (it2 != s.end()) p2 = *it2; else p2 = *s.begin();
            ans = ans + Ask(ys[p1], x) + Ask(ys[p2], x) - Ask(ys[p1], ys[p2]);
            printf("%lld\n", ans);
        }
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值