Loj题解 #10132 异象石

前置知识

  • 倍增/树剖 求LCA
  • set/Splay/平衡树 维护一个时间戳序列

Description

给定一棵 \(n\) 个结点的树,边有边权,三种操作:

  • 标记一个点
  • 取消标记一个点
  • 求使标记的所有点联通最少需要连的边的长度

Solution

一个很神奇的结论

记一个数组 \(dfn\) ,用来存dfs序的每个节点
设现有的被标记的点的dfs序从小到大排序后的集合为 \(\{ x_1, x_2, x_3 ... x_k \}\)
设两个点 \(u, v\) 的距离为 \(dis_{u, v}\)
那么答案为 \(dis_{x_1, x_2} + dis_{x_2 + x_3} + ... + dis_{x_{k - 1}, x_k} + dis_{x_k, x_1}\) 的一半

那么我们用一个数据结构维护标记的点的序列即可,维护的时候顺便统计答案,询问时直接输出
我选择Splay,因为我不会set

对答案的统计:
当标记一个点时,设这个点的dfs序为 \(x\),序列中上一个点为 \(l\),下一个点为 \(r\),那么新增点的贡献为

\[\large dis_{pre_l, pre_x} + dis_{pre_x, pre_r} - dis_{pre_l, pre_r} \]

取消标记一个点的处理方式类似(刚好反着)
注意序列里维护的是结点的dfs序,而求dis是用的是结点的编号
如果序列中没有 \(l\)\(r\) 时注意特判一下

Code

/*
Work by: Suzt_ilymics
Knowledge: ??
Time: O(??)
*/
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#define LL long long
#define int long long
#define orz cout<<"lkp AK IOI!"<<endl

using namespace std;
const int MAXN = 4e5+5;
const int INF = 1e9+7;
const int mod = 1e9+7;

struct edge{
    int to, w, nxt;
}e[MAXN << 1];
int head[MAXN], num_edge;

int n, m, Ans = 0, Cnt = 0;
int dis[MAXN], cnt = 0;
int dep[MAXN], siz[MAXN], son[MAXN], fath[MAXN], top[MAXN], dfn[MAXN], pre[MAXN];

int read(){
    int s = 0, f = 0;
    char ch = getchar();
    while(!isdigit(ch))  f |= (ch == '-'), ch = getchar();
    while(isdigit(ch)) s = (s << 1) + (s << 3) + ch - '0' , ch = getchar();
    return f ? -s : s;
}

void add_edge(int from, int to, int w) { e[++num_edge] = (edge){to, w, head[from]}, head[from] = num_edge; }

namespace Cut{
    void dfs(int u, int fa) {
        dep[u] = dep[fa] + 1, siz[u] = 1, fath[u] = fa;
        for(int i = head[u]; i; i = e[i].nxt) {
            int v = e[i].to;
            if(v == fa) continue;
            dis[v] = dis[u] + e[i].w;
            dfs(v, u);
            siz[u] += siz[v];
            if(siz[son[u]] < siz[v]) son[u] = v;
        }
    }
    void dfs2(int u, int tp) {
        dfn[u] = ++cnt, pre[cnt] = u, top[u] = tp;
        if(son[u]) dfs2(son[u], tp);
        for(int i = head[u]; i; i = e[i].nxt) {
            int v = e[i].to;
            if(v == fath[u] || v == son[u]) continue;
            dfs2(v, v);
        }
    }
    int Get_Lca(int u, int v) {
        while(top[u] != top[v]) dep[top[u]] < dep[top[v]] ? v = fath[top[v]] : u = fath[top[u]];
        return dep[u] < dep[v] ? u : v;
    }
}

namespace Splay{
	#define lson son[now_][0]
	#define rson son[now_][1]
	#define f fa[now_]
	int root, fa[MAXN], son[MAXN][2], node_num = 1;
	int val[MAXN], cnt[MAXN], siz[MAXN];
	int top = 0, bin[MAXN];
	void Push_up(int now_){
		siz[now_] = cnt[now_];
		if(lson) siz[now_] += siz[lson];
		if(rson) siz[now_] += siz[rson]; 
	}
	void Clear(int now_){
		lson = rson = f = val[now_] = cnt[now_] = siz[now_] = 0;
		bin[++top] = now_;
	}
	int NewNode(int fa_, int val_){
		int now_ = (top ? bin[top--] : ++node_num);
		fa[now_] = fa_;
		val[now_] = val_;
		siz[now_] = cnt[now_] = 1;
		return now_; 
	}
	int Whichson(int now_) { return now_ == son[f][1]; }
	void Rotate(int now_){
		int fa_ = f, w = Whichson(now_);
		if(fa[f]) son[fa[f]][Whichson(fa_)] = now_;
		fa[now_] = fa[f];
		
		son[fa_][w] = son[now_][w ^ 1];
		fa[son[fa_][w]] = fa_;
		
		son[now_][w ^ 1] = fa_;
		fa[fa_] = now_;
		Push_up(fa_), Push_up(now_); 
	}
	void Splay(int now_){
		for( ; f; Rotate(now_)){
			if(fa[f]) Rotate(Whichson(now_) == Whichson(f) ? f : now_);
		}
		root = now_;
	}
	void Insert(int now_, int fa_, int val_){
		if(now_ && val[now_] != val_){
			Insert(son[now_][val[now_] < val_], now_, val_);
			return ;
		}
		if(val[now_] == val_) ++ cnt[now_];
		if(!now_){
			now_ = NewNode(fa_, val_);
			if(f) son[f][val[f] < val_] = now_;
		}
		Push_up(now_), Push_up(f), Splay(now_);
	}
	int Find(int now_, int val_){
		if(!now_) return false;
		if(val_ < val[now_]) return Find(son[now_][0], val_);
		if(val_ == val[now_]){
			Splay(now_);
			return true;
		}
		return Find(rson, val_);
	}
	void Delete(int val_){
		if(!Find(root, val_)) return ;
		if(cnt[root] > 1) {
			-- cnt[root];
			Push_up(root);
			return ;
		}
		int oldroot = root;
		if(!son[root][0] && !son[root][1]){
			root = 0;
		} else if(!son[root][0]){
			root = son[root][1], fa[root] = 0;
		} else if(!son[root][1]){
			root = son[root][0], fa[root] = 0;
		} else if(son[root][0] && son[root][1]){
			int leftmax = son[root][0];
			while(son[leftmax][1]) leftmax = son[leftmax][1];
			Splay(leftmax);
			son[root][1] = son[oldroot][1], fa[son[root][1]] = root;
		}
		Clear(oldroot), Push_up(root);
	}
	int QueryRank(int val_){
		Insert(root, 0, val_);
		int ret = siz[son[root][0]] + 1;
		Delete(val_);
		return ret;
	}
	int QueryVal(int rk_){
		int now_ = root;
		while(true){
			if(!now_) return -1;
			if(lson && siz[lson] >= rk_){
				now_ = lson;
			} else{
				rk_ -= lson ? siz[lson] : 0;
				if(rk_ <= cnt[now_]){
					return val[now_];
				}
				rk_ -= cnt[now_];
				now_ = rson;
			}
		}
	}
	int QueryPre(int val_){
		Insert(root, 0, val_);
		int now_ = son[root][0];
		while(rson) now_ = rson;
		Delete(val_);
		return val[now_];
	}
	int QueryNext(int val_){
		Insert(root, 0, val_);
		int now_ = son[root][1];
		while(lson) now_ = lson;
		Delete(val_);
		return val[now_];
	}
}
using namespace Splay;
using namespace Cut;

int Get_dis(int u, int v) { return dis[u] + dis[v] - 2 * dis[Get_Lca(u, v)]; }

signed main()
{
    n = read();
    for(int i = 1, u, v, w; i < n; ++i) {
        u = read(), v = read(), w = read();
        add_edge(u, v, w), add_edge(v, u, w);
    }
    dfs(1, 0), dfs2(1, 1);
    m = read();
    char ch;
    for(int i = 1, x, l, r; i <= m; ++i) {
        cin >> ch;
        if(ch == '+') {
            x = read();
            l = pre[QueryPre(dfn[x])];
            r = pre[QueryNext(dfn[x])];
            if(l) Ans += Get_dis(l, x);
            if(r) Ans += Get_dis(x, r);
            if(l && r) Ans -= Get_dis(l, r);
//            Ans += 2 * dis[Get_Lca(l, r)] + dis[x] - 2 * dis[Get_Lca(l, x)] + dis[x] - 2 * dis[Get_Lca(x, r)];           
            Insert(root, 0, dfn[x]);
        } else if(ch == '-') {
            x = read();
            l = pre[QueryPre(dfn[x])];
            r = pre[QueryNext(dfn[x])];
            if(l) Ans -= Get_dis(l, x);
            if(r) Ans -= Get_dis(x, r);
            if(l && r) Ans += Get_dis(l, r);
//            Ans -= (2 * dis[Get_Lca(l, r)] + dis[x] - 2 * dis[Get_Lca(l, x)] + dis[x] - 2 * dis[Get_Lca(x, r)]);
            Delete(dfn[x]);
        } else {
//            if(Cnt == 1) printf("0\n");
//            else printf("%lld\n", Ans / 2);
            l = pre[QueryVal(1)], r = pre[QueryVal(Cnt)]; // 我选择最后处理序列两端点
            int Res = Ans; //记个变量暂时存储答案
            if(l && r) Res += Get_dis(l, r);
            printf("%lld\n", Res / 2);
        }
    }
    return 0;
}

鸣谢

  • 《信息学奥赛一本通》
原文地址:https://www.cnblogs.com/Silymtics/p/14514629.html