「题目描述」

给定一棵树,树中有一些关键点,每次询问 任取一点出发 **经过所有关键点 **并回到起点的 最短路径长度。

要求支持以下修改:将一个点设为关键点,或者取消一个关键点。

点数 $n \leq 100000$ ,操作数 $m \leq 100000$ 。

「题目链接」

BZOJ 3991 寻宝游戏 「SDOI 2015」

「解题思路」

结论:

一种最优的走法是:将关键点按 DFS 序 排序,以第 1 个点为起点,依次走过所有点,然后回到起点。

证明:还有一些关节没想明白,留坑待补。

这样我们就可以计算答案了,为了支持插入和删除,我们需要动态维护 DFS 序,使用 set 即可。

插入点的时候,查找前趋和后继,从答案中删除前趋后继之间的贡献,然后加入前趋与新点、新点与后继的贡献即可。删除同理。

因为最后还要回到起点,可以把 set 看成环形的,写起来比较方便。

总结:只是关键点总数和点数同阶,但询问很多,不应往树形 DP 方向想;一般要考虑动态维护。

「AC 代码」

#include <cstdio>
#include <algorithm>
#include <set>

typedef long long int64;

const int MAX_N = 100000;
const int MAX_K = 17;

struct Node{
    struct Edge *e;

    int id, depth;
    int64 dist;
    Node *anc[MAX_K + 1];
    bool in;

    Node() : e(NULL), in(false) {}
} nodes[MAX_N];

inline Node* node(int i){
    return &nodes[i];
}

struct Edge{
    Node *to;
    Edge *next;
    int w;

    Edge() {}
    Edge(Node *fr, Node *to, int w) : to(to), next(fr->e), w(w) {}
} edges[2 * (MAX_N - 1)], *ptr = edges;

inline void link(Node *u, Node *v, int w){
    u->e = new (ptr++) Edge(u, v, w);
    v->e = new (ptr++) Edge(v, u, w);
}

Node *seq[MAX_N + 1];
inline void dfs(Node *v, Node *fa){
    static int dfsClock = 0;
    seq[v->id = ++dfsClock] = v;

    v->anc[0] = fa;
    for(int k = 1; k <= MAX_K; k++) v->anc[k] = v->anc[k - 1]->anc[k - 1];

    for(Edge *e = v->e; e; e = e->next) if(e->to != fa){
        e->to->depth = v->depth + 1;
        e->to->dist = v->dist + e->w;

        dfs(e->to, v);
    }
}

inline void dfs(Node *root){
    root->depth = 1, root->dist = 0;
    dfs(root, root);
}

inline Node* lca(Node *u, Node *v){
    if(u->depth < v->depth) std::swap(u, v);
    for(int k = MAX_K; ~k; --k) if(u->anc[k]->depth >= v->depth) u = u->anc[k];
    if(u == v) return u;
    for(int k = MAX_K; ~k; --k) if(u->anc[k] != v->anc[k]) u = u->anc[k], v = v->anc[k];
    return u->anc[0];
}

inline int64 dist(Node *u, Node *v){
    return u->dist + v->dist - 2 * lca(u, v)->dist;
}

struct Manager{
    typedef std::set<int>::const_iterator Iter;

    std::set<int> set;
    int64 ans;

    Manager() : ans(0) {}

    inline void findNeighbor(Iter it, Iter &pre, Iter &suc){
        Iter last = set.end();
        --last;

        if(it == set.begin()) pre = last; else --(pre = it);
        if(it == last) suc = set.begin(); else ++(suc = it);
    }

    inline void insert(Node *v){
        if(set.count(v->id)) return;

        Iter it = set.insert(v->id).first, pre, suc;
        findNeighbor(it, pre, suc);

        ans -= dist(seq[*pre], seq[*suc]);
        ans += dist(seq[*pre], seq[*it]);
        ans += dist(seq[*suc], seq[*it]);
    }

    inline void remove(Node *v){
        Iter it = set.find(v->id), pre, suc;
        findNeighbor(it, pre, suc);

        ans += dist(seq[*pre], seq[*suc]);
        ans -= dist(seq[*pre], seq[*it]);
        ans -= dist(seq[*suc], seq[*it]);

        set.erase(it);
    }

    inline void change(Node *v){
        v->in ^= 1;
        if(v->in) insert(v); else remove(v);
    }

    inline int64 getAns(){
        return ans;
    }
} *gm = new Manager();

int main(){
    int n, m;

    scanf("%d%d", &n, &m);
    for(int i = 0, u, v, w; i < n - 1; i++){
        scanf("%d%d%d", &u, &v, &w), u--, v--;

        link(node(u), node(v), w);
    }

    dfs(nodes);

    for(int i = 0, x; i < m; i++){
        scanf("%d", &x), x--;

        gm->change(node(x));
        printf("%lld\n", gm->getAns());
    }

    return 0;
}

就是这样啦~