虚树相关

「Collection」虚树


BZOJ 2286 消耗战

题意:
  给出一棵树(节点数不超过250000)以及切断每一条边的代价。然后给出一堆询问,每次询问一些关键点,求要使1号点和其他所有关键点不连通,所需的最小代价和。保证1号点不是关键点,且所有询问的关键点数量之和(sum_k)不超过500000。

思路:
  我们考虑单次询问,显然的做法是O((n))的树形DP。但是每次都处理(n)个点是存在很多冗余的,因为所有询问的关键点之和不超过500000。所以我们希望每次询问只提取出与当前询问相关的点进行DP。
  什么叫相关的点呢?设某一次询问的关键点数目为(k​),那么(k​)个关键点一定需要提取出来,两两关键点的lca(lca的数量也是O((k​)))和1号点也需要提取出来。然后我们在这棵新树(节点数O((k​)))上DP即可。
  下面的关键问题是如何建出每次询问的虚树。建树的关键是“维护最左链”。具体来说,我们维护一个栈,这个栈中从底到顶是从浅到深的当前最左链。每次加入一个点(cur),判断它与栈顶节点(pre)的关系。如果栈顶节点是它的祖先,就直接把(cur)压进栈里。否则,就求出(cur)(pre)的LCA,然后把栈中深度大于LCA的点弹出,再把LCA和(cur)压进栈,并更新相关节点的虚树父亲。
  注意,需把所有点的父亲维护出来后再一起建树。建虚树的代码如下:

 inline void build_vt() {
 	int top = 1, tk = k;  // top是栈顶下标,tk是算上所有lca的虚树实际节点数
 	stk[1] = 1;  // 最左链栈
 	fa[1] = 0;  // 虚树中每个点的父亲
 	rep(i, 1, k) {  // 维护最左链
 		int cur = a[i], pre = stk[top];  
 		int lca = get_lca(cur, pre);
 		if (lca == pre) stk[++top] = cur; 
 		else {
 			int dep_lca = dep[lca];
 			while (top > 1 && dep[stk[top]] > dep_lca) top--;
 			if (stk[top] != lca) {  // 如果lca不在栈中
 				fa[stk[top + 1]] = lca;
 				fa[lca] = stk[top];
 				a[++tk] = stk[++top] = lca;
 			}
 			stk[++top] = cur;
 		}
 		fa[cur] = lca;
 	}
    init_graph(tk);  // 初始化前向星
 	rep(i, 1, tk) {
 		int x = a[i];
 		ine(fa[x], x, get_minw(x));  // 加边
 	}
 }

  剩下的部分就是因题而异的DP。完整代码如下:

#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;

 #define rep(i, a, b) for (int i = a; i <= b; i++)
 #define dep(i, a, b) for (int i = a; i >= b; i--)
 #define fill(a, x) memset(a, x, sizeof(a))

 typedef long long LL;

 const int N = 250000 + 5, LG = 23, INF = 0x3f3f3f3f;

 int n, m, k, u, v, w, lg, es, dfs_clock;
 int a[N], pre[N], mark[N], stk[N], dfn[N], dep[N], fa[N];
 int anc[N][LG], minw[N][LG];

 struct Edge{ int to, pre, w; } e[N * 2];
 inline void init_graph(int k) {
 	es = 0;
 	if (!k) fill(pre, 0);
 	else { pre[1] = 0; rep(i, 1, k) pre[a[i]] = 0; }
 }
 inline void ine(int a, int b, int w) {
 	int &i = ++es;
 	e[i].to = b; e[i].w = w;
 	e[i].pre = pre[a];
 	pre[a] = i;
 }
 inline void ine2() {
 	scanf("%d%d%d", &u, &v, &w);
 	ine(u, v, w); ine(v, u, w);
 }
 #define reg(i, x) for (int i = pre[x]; i; i = e[i].pre)

 inline bool cmp(int x, int y) { return dfn[x] < dfn[y]; }
 inline LL min(LL x, LL y) { return x <= y ? x : y; }
 inline int min(int x, int y) { return x <= y ? x : y; }
 inline void swap(int &x, int &y) { int t = x; x = y; y = t; }

 inline void change(int x, int dad, int depth) {
 	dep[x] = depth;
 	dfn[x] = ++dfs_clock;
 	reg(i, x) {
 		int y = e[i].to;
 		if (y == dad) continue;
 		anc[y][0] = x;
 		minw[y][0] = e[i].w;
 		change(y, x, depth + 1);
 	}
 }

 inline void init_lca() {
 	for (lg = 0; (1 << (lg + 1)) <= n; ++lg);
 	anc[1][0] = 0;
 	minw[1][0] = INF;
 	rep(j, 1, lg) rep(i, 1, n) {
 		int pre = anc[i][j - 1];
 		anc[i][j] = anc[pre][j - 1];
 		minw[i][j] = min(minw[i][j - 1], minw[pre][j - 1]);
 	}
 }

 inline int get_lca(int a, int b) {
 	if (dep[a] < dep[b]) swap(a, b);
 	int delta = dep[a] - dep[b];
 	rep(i, 0, lg) if (delta & (1 << i)) a = anc[a][i];
 	dep(i, lg, 0) if (anc[a][i] != anc[b][i]) a = anc[a][i], b = anc[b][i];
 	return a == b ? a : anc[a][0];
 }
 inline int get_minw(int x) {
 	int delta = dep[x] - dep[fa[x]], ret = INF;
 	rep(i, 0, lg) if (delta & (1 << i)) ret = min(ret, minw[x][i]), x = anc[x][i];
 	return ret;
 }
 inline void build_vt() {
 	int top = 1, tk = k;
 	stk[1] = 1;
 	fa[1] = 0;
 	rep(i, 1, k) {
 		int cur = a[i], pre = stk[top];  
 		int lca = get_lca(cur, pre);
 		if (lca == pre) stk[++top] = cur; 
 		else {
 			int dep_lca = dep[lca];
 			while (top > 1 && dep[stk[top]] > dep_lca) top--;
 			if (stk[top] != lca) {
 				fa[stk[top + 1]] = lca;
 				fa[lca] = stk[top];
 				a[++tk] = stk[++top] = lca;
 			}
 			stk[++top] = cur;
 		}
 		fa[cur] = lca;
 	}
    init_graph(tk);
 	rep(i, 1, tk) {
 		int x = a[i];
 		ine(fa[x], x, get_minw(x));
 	}
 }

 inline LL dfs(int x) {
 	LL ret = 0;
 	reg(i, x) {
 		int y = e[i].to, w = e[i].w;
 		if (mark[y] == m) { ret += (LL)w; continue; }
 		ret += min((LL)w, dfs(y));
 	}
 	return ret;
 }

int main()
{
	scanf("%d", &n);
	init_graph(0);
	rep(i, 1, n - 1) ine2();

	dfs_clock = 0;
	change(1, 0, 0);
	init_lca();

	scanf("%d", &m);
	while (m--) {
		scanf("%d", &k);
		rep(i, 1, k) { scanf("%d", &a[i]); mark[a[i]] = m; }
		sort(a + 1, a + k + 1, cmp);
		build_vt();
		printf("%lld
", dfs(1));
	}
	
	return 0;
}


BZOJ 3611 大工程

题意:无权树,每次询问给出一些关键点,询问两两关键点之间的距离和、最近点对距离、最远点对距离。

思路:
  依然是建出虚树后树形DP。

  • 距离和。对于以(x)为根的子树,答案就是所有子子树的答案,加上横跨两子树的答案。前者直接递归求之,后者通过维护“所有(x)子树内关键点到(x)的距离和(代码中为(sumw_x))”以及“(x)子树内的关键点数目即可求之(代码中为(size_x))”。递推式见代码。
  • 最近/远点对。类似地维护“(x)子树内关键点到(x)的最短/长距离(代码中为(minw_x)(maxw_x))即可,还需分类讨论当前点是否是原关键点。细节见代码。

  注意,1号点本身也可能是关键点,所以需注意建虚树时的重复问题。另外,若1号点不是关键点且只有个儿子,则需要以这个儿子作为DP的根节点。代码如下:

#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;

 #define rep(i, a, b) for (int i = a; i <= b; i++)
 #define dep(i, a, b) for (int i = a; i >= b; i--)
 #define fill(a, x) memset(a, x, sizeof(a))
 #define exist(x) mark[x] == q
 #define mp make_pair

 typedef long long LL;

 const int N = 1000000 + 5, LG = 23, INF = 0x3f3f3f3f;

 int n, q, Q, k, u, v, w, lg, es, dfs_clock;
 int ans2, ans3;
 LL ans1, sumw[N];
 int a[N], pre[N], stk[N], dfn[N], dep[N], fa[N];
 int size[N], maxw[N], minw[N], mark[N], son[N];
 int anc[N][LG];

 typedef pair<int, int> Pii;

 struct Edge{ int to, pre, w; } e[N * 2];
 inline void init_graph(int k) {
 	es = 0;
 	if (!k) fill(pre, 0);
 	else { 
 		pre[1] = son[1] = 0;
 		rep(i, 1, k) pre[a[i]] = son[a[i]] = 0;
 	}
 }
 inline void ine(int a, int b, int w) {
 	int &i = ++es;
 	e[i].to = b; e[i].w = w;
 	e[i].pre = pre[a];
 	pre[a] = i;
 }
 inline void ine2() {
 	scanf("%d%d", &u, &v);
 	ine(u, v, 0); ine(v, u, 0);
 }
 #define reg(i, x) for (int i = pre[x]; i; i = e[i].pre)

 inline int min(int x, int y) { return x <= y ? x : y; }
 inline int max(int x, int y) { return x >= y ? x : y; }
 inline bool cmp(int x, int y) { return dfn[x] < dfn[y]; }
 inline void swap(int &x, int &y) { int t = x; x = y; y = t; }

 inline void change(int x, int dad, int depth) {
 	dep[x] = depth;
 	dfn[x] = ++dfs_clock;
 	reg(i, x) {
 		int y = e[i].to;
 		if (y == dad) continue;
 		anc[y][0] = x;
 		change(y, x, depth + 1);
 	}
 }

 inline void init_lca() {
 	for (lg = 0; (1 << (lg + 1)) <= n; ++lg);
 	anc[1][0] = 0;
 	rep(j, 1, lg) rep(i, 1, n)
 		anc[i][j] = anc[anc[i][j - 1]][j - 1];
 }

 inline int get_lca(int a, int b) {
 	if (dep[a] < dep[b]) swap(a, b);
 	int delta = dep[a] - dep[b];
 	rep(i, 0, lg) if (delta & (1 << i)) a = anc[a][i];
 	dep(i, lg, 0) if (anc[a][i] != anc[b][i]) a = anc[a][i], b = anc[b][i];
 	return a == b ? a : anc[a][0];
 }

 inline void build_vt() {
 	int top = 1, tk = k, lower = (a[1] == 1) ? 2 : 1;
 	stk[1] = 1, fa[1] = 0;
 	rep(i, lower, k) {
 		int cur = a[i], pre = stk[top];
 		int lca = get_lca(cur, pre);
 		if (lca == pre) stk[++top] = cur;
 		else {
 			int dep_lca = dep[lca];
 			while (dep[stk[top]] > dep_lca) top--;
 			if (stk[top] != lca) {
 				fa[stk[top + 1]] = lca;
 				fa[lca] = stk[top];
 				a[++tk] = stk[++top] = lca;
 			}
 			stk[++top] = cur;
 		}
 		fa[cur] = lca;
 	}

 	init_graph(tk);
 	rep(i, lower, tk) {
 		int x = a[i], fx = fa[x];
 		int wei = dep[x] - dep[fx];
 		son[fx]++;
 		ine(fx, x, wei);
	}
 }

 inline void update_minp(Pii &ret, int x) {
 	if (x <= ret.first) { ret = mp(x, ret.first); return; }
 	ret.second = min(ret.second, x);
 }
 inline void update_maxp(Pii &ret, int x) {
 	if (x >= ret.first) { ret = mp(x, ret.first); return; }
 	ret.second = max(ret.second, x);
 }
 inline void dfs(int x) {
 	Pii minp = mp(INF, INF), maxp = mp(0, 0);
 	size[x] = exist(x) ? 1 : 0;
 	minw[x] = INF; maxw[x] = 0;
 	sumw[x] = 0LL;
 	reg(i, x) {
 		int y = e[i].to, w = e[i].w;
 		dfs(y);
 		size[x] += size[y];
 		sumw[x] += sumw[y] + 1LL * size[y] * w;
 		minw[x] = min(minw[x], minw[y] + w);
 		maxw[x] = max(maxw[x], maxw[y] + w);
 	}
 	reg(i, x) {
 		int y = e[i].to, w = e[i].w;
 		ans1 += (sumw[y] + 1LL * size[y] * w) * 1LL * (size[x] - size[y]);
		// 横跨两子树的答案:对于每个儿子y,计算sumw[y]被贡献了多少次
 		update_minp(minp, minw[y] + w);
 		update_maxp(maxp, maxw[y] + w);
 	}
 	if (exist(x)) {  // 如果x是关键点,那么minw[x]、maxw[x]也可以用于更新答案
 		ans2 = min(ans2, minw[x]);
 		ans3 = max(ans3, maxw[x]);
 		minw[x] = 0;
 	}
    // 横跨两子树的关键点对
 	ans2 = min(ans2, minp.first + minp.second);
 	ans3 = max(ans3, maxp.first + maxp.second);
 } 

int main()
{
	scanf("%d", &n);

	init_graph(0);
	rep(i, 1, n - 1) ine2();
	change(1, 0, 0);
	init_lca();

	scanf("%d", &Q);
	for (q = 1; q <= Q; q++) {
		scanf("%d", &k);
		rep(i, 1, k) scanf("%d", &a[i]), mark[a[i]] = q;
		sort(a + 1, a + k + 1, cmp);
		build_vt();

		int rt = (son[1] == 1 && !(exist(1))) ? e[pre[1]].to : 1;
		ans1 = 0LL, ans2 = INF, ans3 = 0;
		dfs(rt);
		printf("%lld %d %d
", ans1, ans2, ans3);
	}

	return 0;
}


BZOJ 3991 大工程

题意:
  给定一棵树,每次将某个点设为关键点或取消关键点。对于每次操作后的树,任选一个点作为起点走遍所有关键点后回到起点,求走过路径长度和的最小值。

思路:
  注意无论起点是哪个节点,无论怎么走,答案都是固定的,即虚树上所有边权和的两倍。

  但是,要想直接维护虚树的边非常麻烦。所以我们还是模拟,令起点为DFS序最小的那个虚树节点,于是答案就是(按DfS序排序)相邻两点间的距离和,再加上首尾距离。

  所以我们可以用set维护当前选择的点集。

  • 加入一个点(x)时,设其前驱、后继分别为(p, s),则答案(ans)更新为(ans) - get_len((p, s)) + get_len((p,x)) + get_len((x, s))
  • 删除时,(ans)更新为(ans) - get_len((p, x)) - get_len((x, s)) + get_len((p, s))

  还要注意插入的点在首尾的特殊情况。代码如下:

#include <cstdio>
#include <cstring>
#include <algorithm>
#include <set>
using namespace std;

 #define rep(i, a, b) for (int i = a; i <= b; i++)
 #define dep(i, a, b) for (int i = a; i >= b; i--)
 #define fill(a, x) memset(a, x, sizeof(a))

 typedef long long LL;
 typedef set<int>::iterator sit;

 const int N = 100000 + 5, LG = 21;

 int n, q, x, u, v, w, es, lg, dfs_clock;
 int last[N], dfn[N], dep[N], anc[N][LG];
 bool mark[N];
 LL ans, sumw[N][LG];
 sit it, pre, suf;

 struct Edge{ int to, pre, w; } e[N * 2];
 inline void init_graph() { es = dfs_clock = 0; fill(last, 0); }
 inline void ine(int a, int b, int w) {
 	int &i = ++es;
 	e[i].to = b; e[i].w = w;
 	e[i].pre = last[a];
 	last[a] = i;
 }
 inline void ine2() {
 	scanf("%d%d%d", &u, &v, &w);
 	ine(u, v, w); ine(v, u, w);
 }
 #define reg(i, x) for (int i = last[x]; i; i = e[i].pre)

 inline void swap(int &x, int &y) { int t = x; x = y; y = t; }
 struct cmp { bool operator () (const int &x, const int &y) const { return dfn[x] < dfn[y]; } };
 set<int, cmp> S;

 inline void change(int x, int dad, int depth) {
 	dep[x] = depth;
 	dfn[x] = ++dfs_clock;
 	reg(i, x) {
 		int y = e[i].to, w = e[i].w;
 		if (y == dad) continue;
 		anc[y][0] = x;
 		sumw[y][0] = 1LL * w;
 		change(y, x, depth + 1);
 	}
 }

 inline void init_doubled() {
 	for (lg = 0; (1 << (lg + 1)) <= n; ++lg);
 	anc[1][0] = 0;
 	rep(j, 1, lg) rep(i, 1, n) {
 		int pre = anc[i][j - 1];
 		anc[i][j] = anc[pre][j - 1];
 		sumw[i][j] = sumw[pre][j - 1] + sumw[i][j - 1];
 	}
 }

 inline int get_lca(int a, int b) {
 	if (dep[a] < dep[b]) swap(a, b);
 	int delta = dep[a] - dep[b];
 	rep(i, 0, lg) if (delta & (1 << i)) a = anc[a][i];
 	dep(i, lg, 0) if (anc[a][i] != anc[b][i]) a = anc[a][i], b = anc[b][i];
 	return a == b ? a : anc[a][0];
 }

 inline LL get_len(int x, int y) {
 	int delta = dep[x] - dep[y];
 	LL ret = 0;
 	rep(i, 0, lg) if (delta & (1 << i)) ret += sumw[x][i], x = anc[x][i];
 	return ret;
 }
 inline LL get_path(int x, int y) {
 	int lca = get_lca(x, y);
 	return get_len(x, lca) + get_len(y, lca);
 }

 inline void mark_down(int x) {
 	ans -= get_path(*pre, *suf);
 	ans += get_path(*pre, x);
 	ans += get_path(x, *suf);
 	mark[x] = true;
 }

 inline void clear_mark(int x) {
 	ans -= get_path(*pre, x);
 	ans -= get_path(x, *suf);
 	ans += get_path(*pre, *suf);
 	S.erase(S.find(x));
 	mark[x] = false;
 }

int main()
{
	scanf("%d%d", &n, &q);
	init_graph();
	rep(i, 1, n - 1) ine2();
	change(1, 0, 0);
	init_doubled();

	ans = 0;
	fill(mark, false);
	S.clear();
	rep(i, 1, q) {
		scanf("%d", &x);
		if (S.empty()) { 
			S.insert(x);
			mark[x] = true;
			printf("0
");
			continue;
		}
		if (S.size() == 1 && mark[x] == 1) {
			S.erase(S.find(x));
			mark[x] = false;
			printf("0
");
			continue;
		}
		if (!mark[x]) S.insert(x);
		it = S.find(x);
		if (it == S.begin()) {
 			pre = --S.end();
 			suf = ++it;
 		}
 		else if (it == --S.end()) {
 			pre = --it;
 			suf = S.begin();
 		}
 		else {
 			pre = --it;
 			suf = ++(++it);
 		}
 		if (!mark[x]) mark_down(x);
 		else clear_mark(x);
 		printf("%lld
", ans);
	}

	return 0;
}

Gym 101142 Gangsters in Central City

题意:
  给一棵以(1)号点为根的有根树。规定控制一个点就可以控制它子树中的所有叶子节点(特别地,(1)号点无法控制)。每次添加/删除某个叶子节点的标记,求控制这些叶子节点所需的最少节点数。若有多种方案,选择被控制的未标记叶子节点最少的那一种。

思路:
  不难发现答案不可能超过(1)号点的子节点数。即(ans_1)就是特殊点所在的“根子树”数。至于使(ans_2)最小的方案,把各棵根子树分开考虑,所选的节点就是每棵根子树里的所有标记节点的LCA。插入和删除时,仍然维护一个以DFS序为关键字的set,简单分类讨论后维护答案即可。
  

#include <cstdio>
#include <cstring>
#include <set>
using namespace std;

 #define rep(i, a, b) for (int i = a; i <= b; i++)
 #define dep(i, a, b) for (int i = a; i >= b; i--)
 #define fill(a, x) memset(a, x, sizeof(a))

 typedef set<int>::iterator sit;

 const int N = 100000 + 5, LG = 21;

 int n, q, x, es, lg, dfs_clock, ans1, ans2;
 int sel[N], last[N], dfn[N], bel[N], dep[N], size[N];
 int anc[N][LG];
 char mode;
 sit it;

 struct Edge{ int to, pre; } e[N];
 inline void init_graph() { es = dfs_clock = 0; fill(last, 0); }
 inline void ine(int a, int b) {
 	int &i = ++es;
 	e[i].to = b; e[i].pre = last[a];
 	last[a] = i;
 }
 #define reg(i, x) for (int i = last[x]; i; i = e[i].pre)

 struct cmp { bool operator () (const int &x, const int &y) const { return dfn[x] < dfn[y]; } };
 set<int, cmp> gan[N];

 inline void change(int rt, int x, int depth) {
 	dfn[x] = ++dfs_clock;
 	bel[x] = rt;
 	dep[x] = depth;
 	size[x] = 0;
 	reg(i, x) {
 		int y = e[i].to;
 		change(rt, y, depth + 1);
 		size[x] += size[y];
 		anc[y][0] = x;
 	}
 	if (!size[x]) size[x] = 1;
 }

 inline void swap(int &x, int &y) { int t = x; x = y; y = t; }
 inline void init_lca() {
 	for (lg = 0; (1 << (lg + 1)) <= n; ++lg);
 	anc[1][0] = 0;
 	rep(j, 1, lg) rep(i, 1, n)
 		anc[i][j] = anc[anc[i][j - 1]][j - 1];
 }
 inline int get_lca(int a, int b) {
 	if (dep[a] < dep[b]) swap(a, b);
 	int delta = dep[a] - dep[b];
 	rep(i, 0, lg) if (delta & (1 << i)) a = anc[a][i];
 	dep(i, lg, 0) if (anc[a][i] != anc[b][i]) a = anc[a][i], b = anc[b][i];
 	return a == b ? a : anc[a][0];
 }

 inline void update_ans2(int k, int lca, int mode) {
	ans2 -= (size[sel[k]] - gan[k].size());
 	sel[k] = lca;
 	ans2 += size[lca];
	if (mode == 1) gan[k].insert(x);
	else gan[k].erase(it);
 	ans2 -= gan[k].size();
 }

int main()
{
	freopen("gangsters.in", "r", stdin);
	freopen("gangsters.out", "w", stdout);

	scanf("%d%d", &n, &q);
	init_graph();
	rep(i, 2, n) scanf("%d", &x), ine(x, i);

	ans1 = ans2 = 0;
 	reg(i, 1) {
 		int y = e[i].to;
 		anc[y][0] = 1;
 		change(y, y, 2);
 		gan[y].clear();
 	}
 	init_lca();
 	while (q--) {
 		scanf("
%c %d", &mode, &x);
  		int k = bel[x], lca = -1;
 		if (gan[k].empty()) {
 			gan[k].insert(x);
 			sel[k] = x;
 			printf("%d %d
", ++ans1, ans2);
 			continue;
 		}
 		else if (mode == '-' && gan[k].size() == 1) {
 			gan[k].erase(gan[k].find(x));
 			sel[k] = 0;
 			printf("%d %d
", --ans1, ans2);
 			continue;
 		}
 		else if (mode == '+') {
 			int lca = get_lca(sel[k], x);
 			if (lca == sel[k]) { 
 				gan[k].insert(x);
 				printf("%d %d
", ans1, --ans2);
 				continue;
 			}
 			update_ans2(k, lca, 1);
 		}
 		else {
 			it = gan[k].find(x);
 			if (it == gan[k].begin()) 
 				lca = get_lca(*(++gan[k].begin()), *(--gan[k].end()));
 			else if (it == (--gan[k].end()))
 				lca = get_lca(*(gan[k].begin()), *(--(--gan[k].end())));
 			if (lca == -1 || lca == sel[k]) { 
 				gan[k].erase(it);
 				printf("%d %d
", ans1, ++ans2); 
 				continue; 
 			}
 			update_ans2(k, lca, -1);
 		}
 		printf("%d %d
", ans1, ans2);
 	}

 	return 0;
}

Codeforces 176E Archaeology

  同BZOJ 3991,求的是当前虚树所有边长的和。

#include <cstdio>
#include <cstring>
#include <algorithm>
#include <set>
using namespace std;

 #define rep(i, a, b) for (int i = a; i <= b; i++)
 #define dep(i, a, b) for (int i = a; i >= b; i--)
 #define fill(a, x) memset(a, x, sizeof(a))

 typedef long long LL;
 typedef set<int>::iterator sit;

 const int N = 100000 + 5, LG = 21;

 int n, q, x, u, v, w, es, lg, dfs_clock;
 int last[N], dfn[N], dep[N], anc[N][LG];
 bool mark[N];
 LL ans, sumw[N][LG];
 sit it, pre, suf;
 char mode;

 struct Edge{ int to, pre, w; } e[N * 2];
 inline void init_graph() { es = dfs_clock = 0; fill(last, 0); }
 inline void ine(int a, int b, int w) {
 	int &i = ++es;
 	e[i].to = b; e[i].w = w;
 	e[i].pre = last[a];
 	last[a] = i;
 }
 inline void ine2() {
 	scanf("%d%d%d", &u, &v, &w);
 	ine(u, v, w); ine(v, u, w);
 }
 #define reg(i, x) for (int i = last[x]; i; i = e[i].pre)

 inline void swap(int &x, int &y) { int t = x; x = y; y = t; }
 struct cmp { bool operator () (const int &x, const int &y) const { return dfn[x] < dfn[y]; } };
 set<int, cmp> S;

 inline void change(int x, int dad, int depth) {
 	dep[x] = depth;
 	dfn[x] = ++dfs_clock;
 	reg(i, x) {
 		int y = e[i].to, w = e[i].w;
 		if (y == dad) continue;
 		anc[y][0] = x;
 		sumw[y][0] = 1LL * w;
 		change(y, x, depth + 1);
 	}
 }

 inline void init_doubled() {
 	for (lg = 0; (1 << (lg + 1)) <= n; ++lg);
 	anc[1][0] = 0;
 	rep(j, 1, lg) rep(i, 1, n) {
 		int pre = anc[i][j - 1];
 		anc[i][j] = anc[pre][j - 1];
 		sumw[i][j] = sumw[pre][j - 1] + sumw[i][j - 1];
 	}
 }

 inline int get_lca(int a, int b) {
 	if (dep[a] < dep[b]) swap(a, b);
 	int delta = dep[a] - dep[b];
 	rep(i, 0, lg) if (delta & (1 << i)) a = anc[a][i];
 	dep(i, lg, 0) if (anc[a][i] != anc[b][i]) a = anc[a][i], b = anc[b][i];
 	return a == b ? a : anc[a][0];
 }

 inline LL get_len(int x, int y) {
 	int delta = dep[x] - dep[y];
 	LL ret = 0;
 	rep(i, 0, lg) if (delta & (1 << i)) ret += sumw[x][i], x = anc[x][i];
 	return ret;
 }
 inline LL get_path(int x, int y) {
 	int lca = get_lca(x, y);
 	return get_len(x, lca) + get_len(y, lca);
 }

 inline void mark_down(int x) {
 	ans -= get_path(*pre, *suf);
 	ans += get_path(*pre, x);
 	ans += get_path(x, *suf);
 	mark[x] = true;
 }

 inline void clear_mark(int x) {
 	ans -= get_path(*pre, x);
 	ans -= get_path(x, *suf);
 	ans += get_path(*pre, *suf);
 	S.erase(S.find(x));
 	mark[x] = false;
 }

int main()
{
	scanf("%d", &n);
	init_graph();
	rep(i, 1, n - 1) ine2();
	change(1, 0, 0);
	init_doubled();

	ans = 0;
	fill(mark, false);
	S.clear();
	scanf("%d", &q);
	rep(i, 1, q) {
		scanf("
%c", &mode);
		if (mode == '?') { printf("%lld
", ans >> 1); continue; }
		else scanf("%d", &x);
		if (S.empty()) { 
			S.insert(x);
			mark[x] = true;
			continue;
		}
		if (S.size() == 1 && mark[x] == 1) {
			S.erase(S.find(x));
			mark[x] = false;
			continue;
		}
		if (!mark[x]) S.insert(x);
		it = S.find(x);
		if (it == S.begin()) {
 			pre = --S.end();
 			suf = ++it;
 		}
 		else if (it == --S.end()) {
 			pre = --it;
 			suf = S.begin();
 		}
 		else {
 			pre = --it;
 			suf = ++(++it);
 		}
 		if (!mark[x]) mark_down(x);
 		else clear_mark(x);
	}

	return 0;
}
原文地址:https://www.cnblogs.com/yearwhk/p/6741306.html