[Luogu P3320] [BZOJ 3991] [SDOI2015]寻宝游戏

版权声明:欢迎转载蒟蒻博客,但请注明出处:blog.csdn.net/lpa20020220 https://blog.csdn.net/LPA20020220/article/details/82499006

洛谷传送门

BZOJ传送门

题目描述

小B最近正在玩一个寻宝游戏,这个游戏的地图中有 N 个村庄和 N 1 条道路,并且任何两个村庄之间有且仅有一条路径可达。游戏开始时,玩家可以任意选择一个村庄,瞬间转移到这个村庄,然后可以任意在地图的道路上行走,若走到某个村庄中有宝物,则视为找到该村庄内的宝物,直到找到所有宝物并返回到最初转移到的村庄为止。

小B希望评测一下这个游戏的难度,因此他需要知道玩家找到所有宝物需要行走的最短路程。但是这个游戏中宝物经常变化,有时某个村庄中会突然出现宝物,有时某个村庄内的宝物会突然消失,因此小B需要不断地更新数据,但是小B太懒了,不愿意自己计算,因此他向你求助。为了简化问题,我们认为最开始时所有村庄内均没有宝物

输入输出格式

输入格式:

第一行,两个整数 N M ,其中 M 为宝物的变动次数。接下来的 N 1 行,每行三个整数 x y z ,表示村庄 x y 之间有一条长度为 z 的道路。接下来的 M 行,每行一个整数 t ,表示一个宝物变动的操作。若该操作前村庄 t 内没有宝物,则操作后村庄内有宝物;若该操作前村庄t内有宝物,则操作后村庄内没有宝物。

输出格式:

M 行,每行一个整数,其中第 i 行的整数表示第 i 次操作之后玩家找到所有宝物需要行走的最短路程。若只有一个村庄内有宝物,或者所有村庄内都没有宝物,则输出 0

输入输出样例

输入样例#1:

4 5
1 2 30
2 3 50
2 4 60
2
3
4
2
1

输出样例#1:

0
100
220
220
280

说明

1 N 100000

1 M 100000

对于全部的数据, 1 z 10 9

解题分析

事实上, 从哪个点开始并不重要, 我们只要保证同一子树内的有宝物的点一起访问即可, 这就是 D F S 序的顺序。

那么我们用 s e t 维护有宝物的点的 D F S 序, 插入、 删除时查找前驱后继, 更新它们之间的贡献。 预处理树剖 L C A 和离 1 号点的距离, 这样方便算两点之间距离。

代码如下:

#include <cstdio>
#include <cstring>
#include <cctype>
#include <cmath>
#include <cstdlib>
#include <algorithm>
#include <set>
#define R register
#define IN inline
#define W while
#define MX 100500
#define gc getchar()
template <class T>
IN void in(T &x)
{
    x = 0; R char c = gc;
    for (; !isdigit(c); c = gc);
    for (;  isdigit(c); c = gc)
    x = (x << 1) + (x << 3) + c - 48;
}
bool vis[MX];
int dot, q, arr, cnt;
int dfn[MX], head[MX], dep[MX], son[MX], siz[MX], fat[MX], topf[MX], rec[MX];
long long dis[MX], ans;
struct Edge {int to, len, nex;} edge[MX << 1];
std::set <int> st;
IN void add(R int from, R int to, R int len)
{edge[++cnt] = {to, len, head[from]}, head[from] = cnt;}
void DFS(R int now)
{
    siz[now] = 1; dfn[now] = ++arr; rec[arr] = now;
    for (R int i = head[now]; i; i = edge[i].nex)
    {
        if(edge[i].to == fat[now]) continue;
        dis[edge[i].to] = dis[now] + edge[i].len;
        dep[edge[i].to] = dep[now] + 1;
        fat[edge[i].to] = now;
        DFS(edge[i].to);
        siz[now] += siz[edge[i].to];
        if(siz[edge[i].to] > siz[son[now]]) son[now] = edge[i].to;
    }
}
void DFS(R int now, R int grand)
{
    topf[now] = grand;
    if(!son[now]) return;
    DFS(son[now], grand);
    for (R int i = head[now]; i; i = edge[i].nex)
    {
        if(edge[i].to == fat[now] || edge[i].to == son[now]) continue;
        DFS(edge[i].to, edge[i].to);
    }
}
IN int query(R int x, R int y)
{
     W (topf[x] ^ topf[y])
     {
        if(dep[topf[x]] < dep[topf[y]]) std::swap(x, y);
        x = fat[topf[x]];
     }
     return dep[x] < dep[y] ? x : y;
}
IN long long getdis(R int x, R int y)
{
    int lca = query(x, y);
    return dis[x] + dis[y] - (dis[lca] << 1);
}
std::set <int> :: iterator it, pre, nxt;
int main(void)
{
    int a, b, c;
    in(dot), in(q);
    for (R int i = 1; i < dot; ++i)
    in(a), in(b), in(c), add(a, b, c), add(b, a, c);
    DFS(1), DFS(1, 1); st.insert(0), st.insert(dot + 1);
    W (q--)
    {
        in(a); b = c = 0;
        if(vis[a])
        {
            nxt = pre = it = st.find(dfn[a]); nxt++, pre--; b = *pre, c = *nxt;
            if(b > 0) ans -= getdis(rec[b], a);
            if(c <= dot) ans -= getdis(rec[c], a);
            if(b > 0 && c <= dot) ans += getdis(rec[b], rec[c]);
            st.erase(it);
        }
        else
        {
            st.insert(dfn[a]);
            nxt = pre = it = st.find(dfn[a]); nxt++, pre--; b = *pre, c = *nxt;
            if(b > 0) ans += getdis(rec[b], a);
            if(c <= dot) ans += getdis(rec[c], a);
            if(b > 0 && c <= dot) ans -= getdis(rec[b], rec[c]);
        }
        vis[a] ^= 1; b = *++st.find(0), c = *--st.find(dot + 1);
        printf("%lld\n", ans + (st.size() > 2 ? getdis(rec[b], rec[c]) : 0));//因为要回到原点, 我们需要加上DFS序相差最大的两个点之间的距离
    }
}

猜你喜欢

转载自blog.csdn.net/LPA20020220/article/details/82499006