虚树相关
「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\n", 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\n", 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\n");
continue;
}
if (S.size() == 1 && mark[x] == 1) {
S.erase(S.find(x));
mark[x] = false;
printf("0\n");
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\n", 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("\n%c %d", &mode, &x);
int k = bel[x], lca = -1;
if (gan[k].empty()) {
gan[k].insert(x);
sel[k] = x;
printf("%d %d\n", ++ans1, ans2);
continue;
}
else if (mode == '-' && gan[k].size() == 1) {
gan[k].erase(gan[k].find(x));
sel[k] = 0;
printf("%d %d\n", --ans1, ans2);
continue;
}
else if (mode == '+') {
int lca = get_lca(sel[k], x);
if (lca == sel[k]) {
gan[k].insert(x);
printf("%d %d\n", 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\n", ans1, ++ans2);
continue;
}
update_ans2(k, lca, -1);
}
printf("%d %d\n", 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("\n%c", &mode);
if (mode == '?') { printf("%lld\n", 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;
}