AHOI 2022

vp 成绩:\(100 + 100 + 60 + 45\),呜呜,我好菜。

A. 排列

考虑把排列看成置换,有边 \(i \rightarrow p_i\)\(p_i^{k}\) 事实上往后走 \(k\) 的编号。考虑每个环独立,那每个环限制就是 \(k\) 得是环大小的倍数,那 \(v\) 自然是 LCM 了。

考虑 \(f(i, j) = 0\) 就是他们在一个环里,只有不在一个环里有贡献,这个 swap 相当于是把这两个环合起来,变化的 v 只跟选择的俩环大小有关系,跟具体编号没关系。

一个经典结论是,\(\sum a_i = n\) 的形式,值不同的 \(a_i\) 只有 \(\sqrt{n}\) 量级。 那环本质不同只有根号种,那就可以暴力枚举两个环大小是啥,这样消耗也是 \(O(n)\) 的,然后我们相当于要支持维护一个集合的 LCM,支持删除两个数,加入一个数,新的 LCM。考虑质因数分解每个单独考虑后相当于加入删除,维护 max。因为质因数已经有个 log 了,为了规避掉 log,我们发现最多删两个数,那么只要预先处理最大的三个就好了,新的 max 肯定在这三个后后添加中产生。

\(O(n \log n)\)

// Skyqwq
#include <bits/stdc++.h>

using namespace std;

#define fi first
#define se second
#define pb push_back
#define mp make_pair

typedef long long LL;
typedef pair<int, int> PII;

template <typename T> void inline read(T &x) {
	x = 0; char s = getchar(); int f = 1;
	while (s < '0' || s > '9') { if (s == '-') { f = -1; } s = getchar(); }
	while (s <= '9' && s >= '0') x = x * 10 + (s ^ 48), s = getchar();
	x *= f;
}

template <typename T> bool inline chkMax(T &x, T y) { return y > x ? x = y, 1 : 0; }
template <typename T> bool inline chkMin(T &x, T y) { return y < x ? x = y, 1 : 0; }

const int P = 1e9 + 7, N = 5e5 + 5;

int n, a[N], f[N], sz[N], c[N], d[N], t, inv[N];

int find(int x) {
	return x == f[x] ? x : f[x] = find(f[x]);
}

void inline merge(int x, int y) {
	x = find(x), y = find(y);
	if (x == y) return;
	if (sz[x] > sz[y]) swap(x, y);
	f[x] = y, sz[y] += sz[x];
}


vector<int> g[N];
vector<PII> e[N];


bool st[N];

int pr[N], tot, p0[N];

vector<PII> fc[N];

void inline clr() {
	t = 0;
	for (int i = 1; i <= n; i++) c[i] = d[i] = 0, g[i].clear();
}

int now, cnt[N];

int inline get(int x) {
	int mx = 1;
	for (int v: g[x]) cnt[v]++;
	for (PII v: e[x]) cnt[v.fi] += v.se;
	for (int v: g[x])
		if (cnt[v]) chkMax(mx, v), cnt[v] = 0;
	for (PII v: e[x])
		if (cnt[v.fi]) chkMax(mx, v.fi), cnt[v.fi] = 0;
	return mx;
}

void inline chg(int x, int d) {
	for (PII o: fc[x]) {
		now = 1ll * now * inv[get(o.fi)] % P;
		e[o.fi].pb(mp(o.se, d));
		now = 1ll * now * get(o.fi) % P;
	}
}

void inline del(int x) {
	for (PII o: fc[x])
		e[o.fi].clear();
}

void inline add(int &x, int y) {
	x += y;
	if (x >= P) x -= P;
}

void inline work() {
	now = 1;
	for (int i = 1; i <= n; i++) {
		if (g[i].size()) {
			sort(g[i].begin(), g[i].end(), greater<int>() );
			while (g[i].size() > 3) g[i].pop_back();
			now = 1ll * now * g[i][0] % P;
		}
	}
	int ans = 0;
	for (int i = 1; i <= t; i++) {
		int u = d[i];
		if (c[u] > 1) {
			int la = now;
			chg(u + u, 1);
			chg(u, -1);
			chg(u, -1);
			del(u + u); del(u);
			add(ans, 1ll * now * c[u] % P * (c[u] - 1) % P * u % P * u % P);
			now = la;
		}
		for (int j = i + 1; j <= t; j++) {
			int v = d[j];
			int la = now;
			chg(u + v, 1), chg(u, -1), chg(v, -1);
			add(ans, 2ll * now * c[u] % P * c[v] % P * u % P * v % P);
			del(u + v), del(u), del(v);
			now = la;
		}
	}
	printf("%d\n", ans);
}


void inline apd(int x) {
	for (PII o: fc[x])
		g[o.fi].pb(o.se);
}


void inline prw(int n) {
	p0[1] = 1;
	inv[1] = 1;
	for (int i = 2; i <= n; i++) {
		inv[i] = ((LL)P - P / i) * inv[P % i] % P;
	}
	for (int i = 2; i <= n; i++) {
		if (!st[i]) pr[++tot] = i, p0[i] = i;
		for (int j = 1; pr[j] * i <= n; j++) {
			st[i * pr[j]] = 1;
			p0[i * pr[j]] = pr[j];
			if (i % pr[j] == 0) break;
		}
	}
	for (int i = 1; i <= n; i++) {
		int x = i;
		while (x != 1) {
			int p = p0[x], v = 1;
			while (x % p == 0) v *= p, x /= p;
			fc[i].pb(mp(p, v));
		}
	}
}

int main() {
	//freopen("perm.in", "r", stdin);
	//freopen("perm.out", "w", stdout);
	prw(5e5);
	int T; read(T);
	while (T--) {
		read(n);
		for (int i = 1; i <= n; i++) read(a[i]), f[i] = i, sz[i] = 1;
		for (int i = 1; i <= n; i++) merge(i, a[i]);
		for (int i = 1; i <= n; i++) {
			if (find(i) == i) {
				c[sz[i]]++;
				apd(sz[i]);
			}
		}
		for (int i = 1; i <= n; i++)
			if (c[i]) d[++t] = i;
		work();
		clr();
	}
}

B. 钥匙

这里称 \(1\) 是钥匙,\(2\) 是宝箱。

考虑必须用到最多 \(5\)\(1\) 这个信息,一个大概框架是,你可以预先对于每个 \(2\),枚举所有可能的 \(1\),然后考虑何种路径可以被这对贡献到。

考虑对于每个颜色单独考虑,假设一次经过的长这样 \(11222112\)。考虑把 \(+1\) 的贡献记在 \((1, 4)\) (表示第一位和第四位),\((2, 3),(7, 8)\),这样,这个过程可以把 \(1\) 看成左括号,\(2\) 看成右括号,做括号匹配的过程。

那这样考虑枚举这样的 \(1, 2\) 点对,他能产生贡献的充要条件是:

  • 它在询问路径上
  • 中间部分是正好括号匹配的:(将 \(1\) 看做 \(1\)\(2\) 看做 \(-1\),前缀和 \(\ge 0\),并且和 \(= 0\)

这样设计的贡献是好的,因为已经让互相匹配的尽可能进,而且不重不漏。

在路径上的限制可以通过是否是祖先形式分讨变为询问 \(s, e\) 要分别在 \(dfn\) 的一个区间这种形式,就是所有矩形加,最后单点查,那么离线差分下来树状数组就好了。

枚举 \(1, 2\) 点对的过程比较好的实现方式是,建虚树,然后 dfs?(我能想到的?

\(O(5 n \log n + m\log n)\)

// Skyqwq
#include <bits/stdc++.h>

using namespace std;

#define fi first
#define se second
#define pb push_back
#define mp make_pair

typedef long long LL;
typedef pair<int, int> PII;

template <typename T> void inline read(T &x) {
	x = 0; char s = getchar(); int f = 1;
	while (s < '0' || s > '9') { if (s == '-') { f = -1; } s = getchar(); }
	while (s <= '9' && s >= '0') x = x * 10 + (s ^ 48), s = getchar();
	x *= f;
}

template <typename T> bool inline chkMax(T &x, T y) { return y > x ? x = y, 1 : 0; }
template <typename T> bool inline chkMin(T &x, T y) { return y < x ? x = y, 1 : 0; }

const int N = 5e5 + 5, M = 1e6 + 5;

int n, m, T[N], C[N], tp[N], dfn[N], dfncnt, sz[N], fa[N], son[N], d[N], pre[N];

vector<int> g[N];

void dfs1(int u) {
	sz[u] = 1;
	for (int v: g[u]) {
		if (v == fa[u]) continue;
		fa[v] = u;
		d[v] = d[u] + 1;
		dfs1(v);
		sz[u] += sz[v];
		if (sz[v] > sz[son[u]]) son[u] = v;
	}
}

void dfs2(int u, int top) {
	tp[u] = top, dfn[u] = ++dfncnt;
	pre[dfn[u]] = u;
	if (son[u]) dfs2(son[u], top);
	for (int v: g[u]) {
		if (v == fa[u] || v == son[u]) continue;
		dfs2(v, v);
	}
}

int inline lca(int x, int y) {
	while (tp[x] != tp[y]) {
		if (d[tp[x]] < d[tp[y]]) swap(x, y);
		x = fa[tp[x]];
	}
	return d[x] < d[y] ? x : y;
}

vector<int> b[N];

int inline cmp(int x, int y) {
	return dfn[x] < dfn[y];
}

int s[N], top, ans[M];

vector<int> e[N];

void inline addE(int x, int y) {
	//cout << x << " " << y << " bd?\n";
	e[x].pb(y), e[y].pb(x);
}

vector<int> z;

void inline ins(int x) {
	z.pb(x);
	if (!top)  { s[++top] = x; return; }
	int p = lca(s[top], x);
	while (top > 1 && d[s[top - 1]] >= d[p]) {
		addE(s[top - 1], s[top]);
		top--;
	}
	if (s[top] != p) {
		addE(s[top], p);
		s[top] = p;
		z.pb(p);
	}
	s[++top] = x;
}

int S, nc;

// x is y ancestor?

int inline isA(int x, int y) {
	return dfn[x] <= dfn[y] && dfn[y] < dfn[x] + sz[x];
}

// x -> y subpath cont.

int jp(int x, int y) {
	int la = 0;
	while (tp[y] != tp[x])
		la = tp[x], x = fa[tp[x]];
	if (x == y) return la;
	return pre[dfn[y] + 1];
}

// dfn[s] in [A, B], dfn[e] in [C, D]: +1

vector<PII> t[N];

void inline join(int A, int B, int C, int D) {
	if (A > B || C > D) return;
	t[A].pb(mp(C, 1));
	t[A].pb(mp(D + 1, -1));
	t[B + 1].pb(mp(C, -1));
	t[B + 1].pb(mp(D + 1, 1));
}

void inline insert(int x, int y) {
	if (isA(x, y)) {
		int z = jp(y, x);
		join(1, dfn[z] - 1, dfn[y], dfn[y] + sz[y] - 1);
		join(dfn[z] + sz[z], n, dfn[y], dfn[y] + sz[y] - 1);
	} else if (isA(y, x)) {
		int z = jp(x, y);
		join(dfn[x], dfn[x] + sz[x] - 1, 1, dfn[z] - 1);
		join(dfn[x], dfn[x] + sz[x] - 1, dfn[z] + sz[z], n);
	} else {
		join(dfn[x], dfn[x] + sz[x] - 1, dfn[y], dfn[y] + sz[y] - 1);
	}
}

void dfs(int u, int la, int w) {
	if (w < 0) return;
	for (int v: e[u]) {
		if (v == la) continue;
		if (!w && C[v] == nc && T[v] == 2) {
			insert(S, v);
			continue ;
		}
		int nv = 0;
		if (C[v] == nc) {
			if (T[v] == 2) nv--;
			else nv++;
		}
		dfs(v, u, w + nv);
	}
}

vector<PII> a[N];

int c[N];

void inline add(int x, int y) {
	for (; x <= n; x += x & -x) c[x] += y;
}

int inline ask(int x) {
	int ret = 0;
	for (; x; x -= x & -x) ret += c[x];
	return ret;
}

int main() {
//	freopen("keys.in", "r", stdin);
//	freopen("keys.out", "w", stdout);
	read(n), read(m);
	for (int i = 1; i <= n; i++) read(T[i]), read(C[i]), b[C[i]].pb(i);
	for (int i = 1, u, v; i < n; i++) {
		read(u), read(v);
		g[u].pb(v), g[v].pb(u);
	}	
	dfs1(1);
	dfs2(1, 1);
	for (int i = 1; i <= n; i++) {
		if (!b[i].size()) continue;
		nc = i;
		sort(b[i].begin(), b[i].end(), cmp);
		for (int v: b[i]) ins(v);
		while (top > 1) addE(s[top - 1], s[top]), --top;

		for (int v: b[i])
			if (C[v] == i && T[v] == 1)
				S = v, dfs(v, 0, 0);

		for (int v: z) e[v].clear();
		z.clear();
		top = 0;
	}
	for (int i = 1; i <= m; i++) {
		int u, v; read(u), read(v);
		a[dfn[u]].pb(mp(dfn[v], i));
	}
	for (int i = 1; i <= n; i++) {
		for (PII o: t[i])
			add(o.fi, o.se);
		for (PII o: a[i])
			ans[o.se] = ask(o.fi);
	}
	for (int i = 1; i <= m; i++) printf("%d\n", ans[i]);
}

C. 山河重整

首先这个问题肯定需要转化找到一个更好的充要条件。

  • 对于任意 \(i\),满足 \(S\)\(\le i\) 的和要 \(\ge i\)

证明:这个肯定的必要的,考虑充分怎么证。1, 2 肯定是必需在里面,刚好满足这个条件的,考虑归纳 \(i \ge 3\),假设前 \(i - 1\) 个和都能用 \(\le x\) 表达出来,考虑找到一个最小的 \(x\) 使得 \(\le x\) 的数加起来 \(\ge i\) ,设这个和是 \(t\),那么必然有 \(i \le t < 2i\),那么 \(t - i < i\) 也能被表示了,用总的减去 \(t - i\) 就得到 \(i\) 了。

那么就有一个 \(O(n^2)\) 的 dp,就按 \(1\)\(n\) 顺序加入,记一下当前和(可以对 \(n\) 取 min),任意时刻都得满足限制。

考虑优化这个事情,其实是困难的。(后续全部拟合 ei,呜呜,我好菜。

合法的抽象特征似乎有点难提取,考虑不合法有一个共同的特征,考虑最小的 \(x\),使得 \(\le x\) 的和 \(< x\),那么 \(\le x - 1\) 的和必然是 \(x - 1\)

那么我们可以从新的视角设计状态是 \(f_i\) 表示 \(\le i\) 的一个子集的和恰好是 \(i\),并且任意 \(x \le i\) 都合法(小于等于的和大于自己)。

\(f\) 的计算可以先通过计算 \(f\) 的整数拆分,减去不合法情况。不合法情况是什么呢,其实就是刚才提到全局不合法的一个局部,即这个拆分存在一个 \(x\) 使得 \(\le x\) 的和是 \(x\) 并且之前都合法,那么就是 \(f_x\),然后没有 \(x+1\),加上 \(x + 2 \sim i\) 部分的一个和恰好是 \(i\)

考虑整数拆分的计算,比如现在计算 \(n\) 的分拆,一个发现就是这个集合个数是根号量级的(((咋这么喜欢考这个,那么计数是可以转化角度,一个好的理解是考虑这种矩形图(类似 Ferrers 或者杨表

每列代表一个数,竖着长度代表数值。上图是一个 \(6+5+3+1 = 15\) 的例子。

那么这个矩阵有着最多根号量级的列。之前自然的 dp 都是一列一列加,不妨考虑一行一行加。

假设有 \(p\) 个数,那么一个方案可以表示成 \(n = \sum v_p \times p\) 的形式,期中 \(v_p \ge 1\),而 \(p\) 是根号量级。(这是一个经典映射?

所以可以看成有根号个数的完全背包之类的形式,完成 \(O(n \sqrt {n})\) 分拆的计算。

考虑前面的 \(f\) 对后面 \(f\) 的那个贡献,假设 \(j\)\(i\) 的贡献,那么肯定有 \(j + j + 2 \le i\),其实可以类似的做,我们可以从大到小枚举 \(p\),那么这 \(p\) 个数都有 \(\ge j + 2\),每次先叠行,然后加入 \(f_j\) 后面放 \(p\) 个这样的贡献,这样就是 \(O(n \sqrt{n})\) 。但这要求是前面的 \(f\) 都算好了。

考虑到 \(j \le \frac{i}{2}\),那么我们可以考虑递归的形式,先算好 \(n / 2\) 之前的部分,然后花费 \(O(n \sqrt {n})\) 的代价算之后的部分,这个复杂度仍然是不超过 \(O(n \sqrt {n})\)(类似于倍增 FFT 的过程。

// Skyqwq
#include <bits/stdc++.h>

#define pb push_back
#define fi first
#define se second
#define mp make_pair

using namespace std;

typedef pair<int, int> PII;
typedef long long LL;

template <typename T> bool chkMax(T &x, T y) { return (y > x) ? x = y, 1 : 0; }
template <typename T> bool chkMin(T &x, T y) { return (y < x) ? x = y, 1 : 0; }

template <typename T> void inline read(T &x) {
    int f = 1; x = 0; char s = getchar();
    while (s < '0' || s > '9') { if (s == '-') f = -1; s = getchar(); }
    while (s <= '9' && s >= '0') x = x * 10 + (s ^ 48), s = getchar();
    x *= f;
}

const int N = 5e5 + 5;

int n, P, f[N], g[N], pw[N];

void inline add(int &x, int y) {
	x += y;
	if (x >= P) x -= P;
}

void inline del(int &x, int y) {
	x -= y;
	if (x < 0) x += P;
}

void inline work(int m) {
	if (m <= 1) return;
	work(m / 2);
	for (int i = 0; i <= m; i++) g[i] = 0;

	for (int i = m; i; i--) {
		if (i * (i + 1ll) / 2 > m) continue;
		for (int j = m; j >= i; j--) g[j] = g[j - i];

		for (int j = 0; j + (j + 2) * i <= m; j++)
			add(g[j + (j + 2) * i], f[j]);
		for (int j = i; j <= m; j ++) add(g[j], g[j - i]);
	}
	for (int i = m / 2 + 1; i <= m; i++) del(f[i], g[i]);
}

int main() {
    read(n), read(P);
    for (int i = n; i; i--) {
    	if (i * (i + 1ll) / 2 > n) continue;
    	for (int j = n; j >= i; j--)
    		f[j] = f[j - i];
    	add(f[i], 1);
    	for (int j = i; j <= n; j ++) add(f[j], f[j - i]);
    }	
	f[0] = 1;
	work(n);
	pw[0] = 1;
	for (int i = 1; i <= n; i++) pw[i] = pw[i - 1] * 2 % P;
	int ans = pw[n];
	for (int i = 0; i < n; i++) {
		del(ans, 1ll * f[i] * pw[n - i - 1] % P);
	}
	printf("%d\n", ans);
    return 0;
}

D. 回忆

生活在题面里的他们,是一群怪异的少年。

对城市中修建道路需满足的基本物理限制熟视无睹,沉迷于十万个城市、百万条道路上的各种结构。

明明知道真正需要的数字庞大到无法计算,却偏要关心它模一个奇怪素数之后得到的结果。

如此智力超群的他们,却总是在自己提出的诡异的问题下败下阵来,把它们一股脑地丢给你们来做。

如今,他们长大了。他们学习到更普适的理论,习惯了更抽象的符号,不必再思考如此古怪的问题。但他们不曾料到,你们却以这些 “无用” 的问题为驱动,于计算机学科体系的一隅,开垦出了一片独属于 OI 的新天地。

有一天,他们各自回忆起了少年时期提出的问题。

贺 ix35 题解,这就是我

这个大致思路在 vp 的时候考量过,在写 B 子任务时,但写了一下,写挂了,对于免费提供的(从上面,很没理解,那实际上你可以看做换根,ans = 所有的 - 两两匹配上的接口。

然后万万没想到,去掉包含关系后有这么好的性质。

  • \(t\) 两两不同。这我都不知道?????????????????

然后考虑这个做法的细节。

  • 如果能平分拼起来,你贡献首先是加入 sum,然后 (sum + 免费)/ 2 拼起来的减省。

  • 首先你需要维护这个 \(z\),考虑这个 \(z\) 在过程中怎么变。你会走一条链,每次去掉 \(s = p\) 这个点的某些研究。那么你考虑,你相当于是给了一条免费路径,那你之后的拼接不会考虑他拼起来,那相当于是查到这个子树时,会 \(-1\)!!!!!!!!!!

  • 维护的特殊标记点两两不交。

  • 从这个点跳到重儿子时,如果重儿子是特殊标记点,清除就好了。

还是很没理解,我该怎么办??

// Skyqwq
#include <bits/stdc++.h>

#define pb push_back
#define fi first
#define se second
#define mp make_pair

using namespace std;

typedef pair<int, int> PII;
typedef long long LL;

template <typename T> bool chkMax(T &x, T y) { return (y > x) ? x = y, 1 : 0; }
template <typename T> bool chkMin(T &x, T y) { return (y < x) ? x = y, 1 : 0; }

template <typename T> void inline read(T &x) {
    int f = 1; x = 0; char s = getchar();
    while (s < '0' || s > '9') { if (s == '-') f = -1; s = getchar(); }
    while (s <= '9' && s >= '0') x = x * 10 + (s ^ 48), s = getchar();
    x *= f;
}

const int N = 2e5 + 5;

int n, m, dfn[N], fa[N], dfncnt, L[N], R[N], sz[N];

vector<int> g[N], w[N];

struct BIT{
	int c[N];
	void inline add(int x, int k) {
		for (; x <= n; x += x & -x) c[x] += k;
	}

	int inline ask(int x) {
		int ret = 0;
		for (; x; x -= x & -x) ret += c[x];
		return ret;
	}
	void inline clr() {
		for (int i = 1; i <= n; i++) c[i] = 0;
	}
} t1, t2;

struct E{
	int s, t;
} e[N];


bool vis[N];

int fr[N];

void inline clr() {
	dfncnt = 0;
	t1.clr(), t2.clr();
	for (int i = 1; i <= n; i++) vis[i] = fr[i] = 0, g[i].clear(), w[i].clear(), fa[i] = sz[i] = dfn[i] = 0;
}

void dfs1(int u) {
	dfn[u] = ++dfncnt;
	sz[u] = 1;
	L[u] = dfn[u];
	for (int v: g[u]) {
		if (v == fa[u]) continue;
		fa[v] = u;
		dfs1(v);
		sz[u] += sz[v];
	}
	R[u] = dfncnt;
}

bool inline cmp(E x, E y) {
	if (dfn[x.s] != dfn[y.s]) return dfn[x.s] < dfn[y.s];
	return dfn[x.t] > dfn[y.t];
}

bool dfs2(int u) {
	bool o = 0;
	for (int v: g[u]) {
		if (v == fa[u]) continue;
		o |= dfs2(v);
	}
	if (!o && vis[u]) t1.add(dfn[u], 1);
	return o | vis[u];
}

void solve() {
	int p = 1, re = 0, ans = 0;
	while (p) {
		int sum = 0, mx = 0, big = 0;
		for (int v: g[p]) {
			if (v != fa[p]) {
				int w = t1.ask(R[v]) - t1.ask(L[v] - 1);
				if (chkMax(mx, w)) big = v;
				sum += w;
			}
		}
		int rt = sum - mx;
		int rs = rt + re;
		if (mx <= rs) {
			ans += sum - (sum + re) / 2;
			break;
		}
		ans += rt;
		re += rt;
		for (int v: w[p]) {
			if (dfn[v] < L[big] || dfn[v] > R[big]) continue;
			int z = t2.ask(dfn[v]);
			if (z) {
				t1.add(dfn[z], 1);
				t2.add(L[z], -z), t2.add(R[z] + 1, z);
				fr[z]--;
			} else if (re) {
				re--;
			} else {
				ans++;
			}
			t1.add(dfn[v], -1);
			t2.add(L[v], v), t2.add(R[v] + 1, -v);
			fr[v]++;
		}
		if (fr[big]) t2.add(L[big], -big), t2.add(R[big] + 1, big);
		p = big;
		re += fr[big], fr[big] = 0;
	}
	printf("%d\n", ans);
}

void inline work() {
	read(n), read(m);
	for (int i = 1, u, v; i < n; i++) 
		read(u), read(v), g[u].pb(v), g[v].pb(u);
	for (int i = 1; i <= m; i++) read(e[i].s), read(e[i].t);
	dfs1(1);
	// 清除包含关系
	sort(e + 1, e + 1 + m, cmp);
	for (int i = 1; i <= m; i++) {
		int cnt = t1.ask(R[e[i].t]) - t1.ask(L[e[i].t] - 1);
		if (!cnt) {
			w[e[i].s].pb(e[i].t);
			vis[e[i].t] = 1;
		}
		t1.add(dfn[e[i].t], 1);
	}
	t1.clr();
	// 维护 t1,维护在最底下、未解决的 t (s 在当前子树里)
	dfs2(1);
	// 维护 t2,维护那些互不成祖先关系的特殊点,覆盖他们的子树,帮助快速查
	solve();
	clr();
}

int main() {
	// freopen("memory6.in", "r", stdin);
	// freopen("m.out", "w", stdout);
    int Case; read(Case);
    while (Case--) work();
    return 0;
}
posted @ 2022-05-10 11:02  DMoRanSky  阅读(340)  评论(0编辑  收藏  举报