誰にも掴めない風になりたい

6 月 10 日 ~ 6 月 24 日

被压力学考了,顺便考了一下 MO 预赛,所以还是没几个题

CF 1830D Mex Tree

给定一棵 \(n\) 个结点的树,求一种给点赋权 \(0\)\(1\) 的方案,使得所有路径的点权 \(\operatorname{mex}\) 之和最大。你只需要求出这个最大值。\(n\le 2\times 10^5\)


感性地想,我们希望让 \(\operatorname{mex} = 2\) 的链尽可能多,这用二分图染色就可以做到,于是我们得到了答案的一个下界 \(n(n-1) + \mathcal{O}(n)\)

但有时候这并不是最优的:我们把答案分成两部分,长度 \(\geq 2\) 的链的贡献 \(A\) 和点的贡献 \(B\),则二分图染色相当于是直接令 \(A = n(n-1)\)。如果用很少的 \(A\) 的代价就能够换来很多的 \(B\),那么在最优的方案中就可能有 \(A < n(n-1)\)。一个简单的例子是边 \((1,2)\) 两端都挂着巨大多点的树,最优的方案是令 \(a_1=a_2=1\),其余点为 \(0\),此时我们花了 \(A\)\(1\) 的代价,但是收获了巨大多 \(B\) 的贡献。

注意到,\(B\)\(\mathcal{O}(n)\) 的,因此在最优方案中,\(A\) 减少的代价也至多是 \(\mathcal{O}(n)\)。这意味着,在最优方案中,所有同色连通块的大小都不会超过 \(\mathcal{O}(\sqrt n)\)!于是可以树形 DP,设 \(f_{i,j,k}\) 为点 \(i\) 颜色为 \(k \in \{0,1\}\),子树内 \(i\) 所在同色连通块大小为 \(j\)\(A\)\(B\) 的代价和的最小值。转移就是一个背包,但是这题卡空间,需要用 vector 存 DP 答案并在计算完成后立即销毁,总时间复杂度 \(\mathcal{O}(n \sqrt n)\)

code
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
constexpr int N = 2e5 + 5, B = 5e2;
constexpr LL inf = 1e18;
int n, sz[N]; vector <int> e[N];
LL ans, tmp[B + 5][2]; vector <LL> f[N][2];
void dfs(int u, int ff) {
	sz[u] = 1;
	f[u][0].push_back(inf), f[u][0].push_back(0);
	f[u][1].push_back(inf), f[u][1].push_back(1);
	for (auto v : e[u]) if (v != ff) {
		dfs(v, u);
		LL mi0 = inf, mi1 = inf;
		for (int i = 1; i <= min(B, sz[v]); i++) mi0 = min(mi0, f[v][0][i]), mi1 = min(mi1, f[v][1][i]);
		for (int i = 1; i <= min(B, sz[u]); i++) {
			tmp[i][0] = f[u][0][i], f[u][0][i] = inf;
			tmp[i][1] = f[u][1][i], f[u][1][i] = inf;
		}
		for (int i = min(B, sz[u]) + 1; i <= min(B, sz[u] + sz[v]); i++) f[u][0].push_back(inf), f[u][1].push_back(inf);
		for (int i = 1; i <= min(B, sz[u]); i++) {
			for (int j = 1; j <= min(B, sz[v]) && i + j <= B; j++) {
				f[u][0][i + j] = min(f[u][0][i + j], tmp[i][0] + f[v][0][j] + 1LL * i * j);
				f[u][1][i + j] = min(f[u][1][i + j], tmp[i][1] + f[v][1][j] + 2LL * i * j);
			}
		}
		for (int i = 1; i <= min(B, sz[u]); i++) {
			f[u][0][i] = min(f[u][0][i], tmp[i][0] + mi1);
			f[u][1][i] = min(f[u][1][i], tmp[i][1] + mi0);
		}
		sz[u] += sz[v];
		f[v][0].clear(), f[v][0].shrink_to_fit();
		f[v][1].clear(), f[v][1].shrink_to_fit();
 	}
}
void clr() {
	ans = 0;
	for (int i = 1; i <= n; i++) {
		e[i].clear();
		f[i][0].clear(), f[i][0].shrink_to_fit();
		f[i][1].clear(), f[i][1].shrink_to_fit();
	}
}
void solve() {
	cin >> n;
	for (int i = 1, x, y; i < n; i++) {
		cin >> x >> y;
		e[x].push_back(y);
		e[y].push_back(x);
	}
	ans = inf;
	dfs(1, 0);
	for (int i = 1; i <= min(n, B); i++) ans = min(ans, min(f[1][0][i], f[1][1][i]));
	ans = 1LL * n * n - ans;
	cout << ans << "\n";
	clr();
}
signed main() {
	ios :: sync_with_stdio(false);
	cin.tie(nullptr);
	int t; cin >> t;
	while (t--) solve();
	return 0;
}

CF 1830E Bully Sort

给定一个长为 \(n\) 的排列 \(p\)。定义如下过程为一次操作:

  • \(i\) 为最大的满足 \(i \neq p_i\) 的元素 \(p_i\) 所在的位置,\(j\) 为最小的满足 \(j > i\) 的元素 \(p_j\) 所在的位置,交换 \((p_i,p_j)\)

定义 \(f(p)\) 为将 \(p\) 排序所需要的操作数。\(q\) 次操作,每次操作给定 \(x,y\),交换 \((p_x,p_y)\)。你需要在每次操作后求出 \(f(p)\)
\(n \leq 5 \times 10^5\)\(q \leq 5 \times 10^4\),时限 \(\text{10.0s}\)


先考虑怎么求 \(f(p)\)。若 \(p_n=n\),则令 \(n\) 减去 \(1\)。否则一次操作相当于交换 \(n\) 和它后面最小的数。

发现直接考虑一个数的移动次数或者每个位置被操作的次数都很困难,我们尝试找到一些容易计算的量,并发掘其和所求量之间的关系。

注意到一个关键性质:设 \(n\) 的位置为 \(i\),它后面最小的数位置为 \(j\),那么一定有 \(p_j \leq i\),即 \(p_j\) 一定不会向右移动。这是因为 \(i \sim n\) 中共有 \(n-i+1\) 个互不相同的数,所以 \(p_{i \sim n}\) 的最小值一定不大于 \(i\)。此时我们考虑一次交换:设 \(k = j-i\),那么它使 \(\sum|i - p_i|\) 减少了 \(2k\),而逆序对数减少了 \(2k-1\)。又因为最终 \(\sum|i-p_i|\) 和逆序对数均为 \(0\),因此操作次数 \(f(p) = \sum |i-p_i| - \sum_{i<j}[p_i > p_j]\)

前半部分容易维护,后半部分先预处理出逆序对数,每次更新需要查询区间内 \(< x\)\(> x\) 的数的个数。考虑分块,维护每个块内排好序的数组,查询时散块暴力整块二分,取 \(B = \sqrt{n \log \sqrt n}\) 得时间复杂度 \(\mathcal{O}(n \log n + q\sqrt{n \log \sqrt n})\)

code
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
constexpr int N = 5e5, B = 2.3e3, M = N / B + 5;
int n, m, q, a[N + 5];
int bl[M], br[M], len[M], bid[N], t[M][B + 5];
struct BIT {
	int c[N];
	void upd(int x, int v) {
		for (int i = x; i <= n; i += i & -i) c[i] += v;
	}
	int qry(int x) {
		int res = 0;
		for (int i = x; i; i -= i & -i) res += c[i];
		return res;
	}
} tr;
void mdf(int x, int y) {
	int l = bid[x], r = bid[y];
	if (l == r) 
		return swap(a[x], a[y]);
	int dl = lower_bound(t[l] + 1, t[l] + len[l] + 1, a[x]) - t[l];
	int dr = lower_bound(t[r] + 1, t[r] + len[r] + 1, a[y]) - t[r];
	
	for (int i = dl; i < len[l]; i++) 
		t[l][i] = t[l][i + 1];
		
	for (int i = dr; i < len[r]; i++) 
		t[r][i] = t[r][i + 1];
	
	swap(a[x], a[y]);
	dl = lower_bound(t[l] + 1, t[l] + len[l], a[x]) - t[l];
	dr = lower_bound(t[r] + 1, t[r] + len[r], a[y]) - t[r];
	
	for (int i = len[l]; i > dl; i--) 
		t[l][i] = t[l][i - 1];
		
	for (int i = len[r]; i > dr; i--) 
		t[r][i] = t[r][i - 1];
		
	t[l][dl] = a[x];
	t[r][dr] = a[y];
}
int qry(int l, int r) {
	int res = 0;
	int L = bid[l], R = bid[r];
	if (L == R) {
		for (int i = l + 1; i <= r - 1; i++) {
			if (a[l] > a[i]) res++;
			if (a[i] > a[r]) res++; 
		}
		if (a[l] > a[r]) res++;
		return res;
	}
	for (int i = l + 1; i <= br[L]; i++) {
		if (a[l] > a[i]) res++;
		if (a[i] > a[r]) res++;
	} 
	for (int i = bl[R]; i <= r - 1; i++) {
		if (a[l] > a[i]) res++;
		if (a[i] > a[r]) res++;
	}
	if (a[l] > a[r]) res++;
	for (int i = L + 1; i <= R - 1; i++) {
		int dl = lower_bound(t[i] + 1, t[i] + len[i] + 1, a[l]) - t[i];
		int dr = lower_bound(t[i] + 1, t[i] + len[i] + 1, a[r]) - t[i];
		res += dl - 1;
		res += (len[i] - dr + 1);
	}
	return res;
}
signed main() {
	ios :: sync_with_stdio(false);
	cin.tie(nullptr);
	cin >> n >> q;
	for (int i = 1; i <= n; i++) cin >> a[i];
	LL ans = 0;
	for (int i = 1; i <= n; i++) ans += abs(i - a[i]);
	for (int i = n; i >= 1; i--) {
		ans -= tr.qry(a[i] - 1);
		tr.upd(a[i], 1);
	}
	while (br[m] < n) {
		m++;
		bl[m] = br[m - 1] + 1, br[m] = min(n, bl[m] + B - 1), len[m] = (br[m] - bl[m] + 1);
		for (int i = bl[m]; i <= br[m]; i++) {
			bid[i] = m;
			t[m][i - bl[m] + 1] = a[i];
		}
		sort(t[m] + 1, t[m] + len[m] + 1);
	}
	while (q--) {
		int x, y;
		cin >> x >> y;
		ans -= abs(x - a[x]);
		ans -= abs(y - a[y]);
		ans += qry(x, y);
		
		mdf(x, y);
		
		ans += abs(x - a[x]);
		ans += abs(y - a[y]);
		ans -= qry(x, y);
		cout << ans << "\n";
	}
	return 0;
}

UOJ 805【UR #25】设计草图

给定一个只包含 ()? 构成的字符串 \(S\)。称一个区间 \([l,r]\) 是有救的,当且仅当存在一种将该区间内的 ? 替换为 () 的方式,使得区间内的字符能按顺序组成一个合法的括号序列。求 \(S\) 有多少个区间有救。\(n \leq 10^6\)


容斥成算没救的区间个数,我们考虑什么时候一个区间肯定是没救的。

( 视为 \(1\)) 视为 \(-1\),对于一个区间 \([l,r]\),如果将其中所有 ? 都换成 ( 后仍然存在某个前缀和 \(<0\),或将其中所有 ? 都换成 ) 后仍存在某个后缀和 \(<0\),那么这个区间就肯定是没救的。当然,长度为奇数的区间显然也是没救的。

然后我们发现这个条件好像是充要的。考虑枚举左端点,计算有多少个右端点对应的区间没救。对于每个位置 \(i\),我们可以预处理出最小的位置 \(c_i\) 表示当 \(j > c_i\) 时,区间 \([i,j]\) 满足将其中所有 ? 都换成 ( 后仍然存在某个前缀和 \(<0\),同理预处理出最大的位置 \(d_i\) 表示当 \(j < d_i\) 时,区间 \([j,i]\) 将其中所有 ? 都换成 ) 后仍存在某个后缀和 \(<0\),那么对于当前的 \(i\)\(c_i\) 之后的部分就不用管了,而对于 \(j \in [i,c_i]\),如果 \(d_j > i\),那么以 \(j\) 为右端点同样没救——这是一个二维数点问题,扫描线树状数组即可,注意由于存在长度不为奇数的限制,需要用两颗树状数组分别维护奇数位和偶数位。

关于预处理:以 \(c_i\) 为例,先把所有 ? 换成 ( 做一遍前缀和,那么对于 \([i,c_i]\),如果 \(c_i\) 不为 \(n\),那么 \([i,c_i]\) 一定是一个合法括号串,且 \(c_i + 1\) 位是 )。倒着枚举,对于每个数 \(j\) 开个桶维护上一个前缀和为 \(j\),且下一位是 ) 的位置 \(t_j\) 即可。当然也可以直接栈模拟括号匹配,做的时候犯蠢了。发现容斥了个锤子,直接算就行了,时间复杂度 \(\mathcal{O}(n \log n)\)

code
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
constexpr int N = 1e6 + 5;
int n; string str;
int sum[N], c[N], d[N], tmp[N * 2]; LL ans;
vector <int> v[N];
struct BIT {
	int c[N];
	void mdf(int x, int v) {
		for (int i = x; i <= n; i += i & -i) c[i] += v;
	}
	int qry(int x) {
		int ret = 0;
		for (int i = x; i; i -= i & -i) ret += c[i];
		return ret;
	}
} t0, t1;
int calc(int l, int r) {
	return (r - l + 1) / 2;
}
signed main() {
	ios :: sync_with_stdio(false);
	cin.tie(nullptr);
	cin >> str;
	str = ' ' + str;
	n = str.length() - 1;
	for (int i = 1; i <= n; i++) sum[i] = sum[i - 1] + (str[i] == ')' ? -1 : 1);
	int mi = n;
	for (int i = n; i >= 1; i--) {
		mi = min(mi, sum[i]);
		if (str[i] == ')') c[i] = -1;
		else if (!tmp[sum[i - 1] + n]) c[i] = n;
		else c[i] = tmp[sum[i - 1] + n];
		if (str[i + 1] == ')') tmp[sum[i] + n] = i;
	}	
	fill(tmp + 1, tmp + 2 * n + 1, 0);
	for (int i = n; i >= 1; i--) sum[i] = sum[i + 1] + (str[i] == '(' ? -1 : 1);
	for (int i = 1; i <= n; i++) {
		if (str[i] == '(') d[i] = i + 1;
		else if (!tmp[sum[i + 1] + n]) d[i] = 1;
		else d[i] = tmp[sum[i + 1] + n];
		if (str[i - 1] == '(') tmp[sum[i] + n] = i;
	}
	for (int i = 1; i <= n; i++) if (d[i] != -1) v[d[i] - 1].push_back(i);
	for (int i = n; i >= 1; i--) {
		if (c[i] == -1) continue;
		ans += calc(i, c[i]);
		for (auto it : v[i]) {
			if (it % 2 == 0) t0.mdf(it, 1);
			if (it % 2 == 1) t1.mdf(it, 1);
		}
		if (i % 2 == 0) ans -= t1.qry(c[i]);
		if (i % 2 == 1) ans -= t0.qry(c[i]);
	}
	cout << ans << "\n";
	return 0;
}

UOJ 806【UR #25】见贤思齐

\(n\) 个点,每个点有出边 \(i \to p_i\),权值 \(a_i\)。每个时刻,若 \(a_{p_i} \geq a_i\),那么在下个时刻中 \(a'_i \gets a_i + 1\)\(q\) 次查询 \(x,y\),求 \(x\) 号点在第 \(y\) 个时刻后的权值。\(n,q,y \leq 2 \times 10^5\)\(a_i \leq 10^9\)


做法 from zky,感觉有点外星了。

\(f(x,t)\)\(x\) 号点 \(t\) 时刻后的水平。容易发现 \(f(x,t)\) 在开始的一段时间里是 \(a_x\)\(a_x + t\),之后的一段时间是 \(f(p_x,t-1)+1\)。更具体地,对于 \(t > 0\),有 \(f(x,t) = \max(a_x,\min(f(p_x,t-1)+1,a_x+t))\)

然后比较神秘的一步出现了,设 \(g(x,t) = f(x,t) - t\),那么就有 \(g(x,t) = \max(a_x-t,\min(g(p_x,t-1),a_x))\)

直接维护 \(g(x,t)\) 好像不太好做,我们考虑把计算的过程迭代展开,相当于是要维护一个变量 \(c\),初始时 \(x=a_0\),然后依次通过 \(k = dep_x\) 个区间 \([l_i,r_i]\)(其中 \(l_i = a_i-t',r_i = a_i\)),若 \(c > l_i\),则令 \(c' \gets l_i\),若 \(c < r_i\),则令 \(c' \gets r_i\)

此时我们考虑最短的后缀 \(i \sim k\) 满足 \(\max\{a_j - t'\} \geq \min\{a_j\}\),有两种情况:即 \([l_i,r_i]\) 在后面某个区间 \([l_j,r_j]\) 的上方或者下方。不妨假设在下方,那么对于 \([l_i,r_i]\),无论经过这个区间后 \(c\) 的值是什么,首先显然它不会下降,否则这和 \(i \sim k\) 是最短后缀矛盾。接着在一通操作之后它会变成 \(\max\{a_j - t'\}\),而由于后缀的区间交集不为空,因此之后 \(c\) 的值就不会变了。也就是说此时答案事实上就是 \(\max\{a_j-t'\}\)。对于在上方的情况同理。

而一段后缀在树上就是 \(x\) 往上的一段祖先,因此容易使用倍增维护上述过程。总时间复杂度 \(\mathcal{O}((n+q) \log n)\)

code
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
constexpr int N = 2e5 + 5, inf = 1e9;
int n, q, a[N], p[N];
int up[20][N], up1[20][N], up2[20][N];
signed main() {
	ios :: sync_with_stdio(false);
	cin.tie(nullptr);
	cin >> n >> q;
	for (int i = 1; i <= n; i++) cin >> a[i];
	for (int i = 1; i <= n; i++) cin >> p[i];
	for (int i = 1; i <= n; i++) {
		up[0][i] = p[i];
		up1[0][i] = a[i];
		up2[0][i] = a[i];
	}
	for (int j = 1; j <= 19; j++) {
		for (int i = 1; i <= n; i++) {
			int p = up[j - 1][i];
			up[j][i] = up[j - 1][p];
			up1[j][i] = max(up1[j - 1][i], up1[j - 1][p] + (1 << (j - 1)));
			up2[j][i] = min(up2[j - 1][i], up2[j - 1][p]);
		}
	}
	while (q--) {
		int x, d; 
		cin >> x >> d;
		int l = -1e9, r = 1e9;
		int cur = 0;
		for (int i = 19; i >= 0; i--) {
			if (cur + (1 << i) <= d) {
				int nl = max(l, up1[i][x] - d + cur);
				int nr = min(r, up2[i][x]);
				if (nl >= nr) {
					continue;
				}
				l = nl;
				r = nr;
				x = up[i][x];
				cur += 1 << i;
			}
		}
		l = max(l, a[x] - d + cur);
		if (l >= r) {
			cout << r + d << "\n";
			continue;
		}
		r = min(r, a[x]);
		if (l >= r) {
			cout << l + d << "\n";
			continue;
		} 
	}
 	return 0;
}

LG P9393 紫丁香

\(S\) 是任意长度为 \(m\)\(01\) 串。有 \(n\) 个操作,每个操作会将 \(S\) 的一些位赋值为 \(0\),一些位赋值为 \(1\),还有一些位不变。 \(q\) 次操作,每次操作给定一个长度为 \(m\)\(01\)\(S\),你可以对它以任意顺序做任意多次操作,得到的串 \(S'\) 可以被看做一个二进制数,求对应二进制数最大的 \(S'\)\(m \leq 22\)\(n,q \leq 10^5\)


先考虑怎么处理一次询问。

二分答案,问题转化为给定一个位的集合 \(B\),求是否存在一种方式使得操作完后 \(B\) 中的位全为 \(1\)。我们可以将 \(B\) 的限制写成一个由 1* 组成的串 \(R\),为 1 的位表示操作完后这一位要是 \(1\),为 * 的位表示对这一位没有限制。

由于操作的形式是覆盖,我们不妨倒着考虑。考虑最后一次操作,设其对应的串为 \(T_i\),那么对于 \(R\) 中一个为 * 的位,\(T_i\) 中可以是 01- 中的任意一个。而对于 \(R\) 中一个为 1 的位, \(T_i\) 中只可能是 1-。假设某个 \(R\) 中为 1 的位在 \(T_i\) 中也是 1,那么这一位在更之前的操作中就没有限制了,于是我们可以在 \(R\) 中把它改成 *

于是我们可以按照如下过程进行判定:每次选一个合法的 \(T_i\),然后将 \(R,T_i\) 都为 1 的位在 \(R\) 中改为 *,重复此过程直到 \(R\) 无法再被更新。最终,\(R\) 中可能还会剩下一些 1,我们只需要检验这些位置在 \(S\) 中是否都为 1 即可。

注意到,上述过程除了最后一步之外之和 \(R\) 有关,而 \(R\) 只有 \(2^m\) 种,我们不妨对 \(R\) 进行 DP:设 \(f(R)\)\(R\) 通过上述过程能得到的 1 的数量最小的串,\(g(R)\)\(R\) 通过一次操作能够变成 * 的位的并,那么有 \(f(R) = f(R \oplus g(R))\)。而 \(g(R)\) 的计算是简单的:对于操作 \(T\),设把 \(T\) 中的 * 全改为 1 后的串为 \(T0\),我们把 \(T\) 挂在 \(g(T0)\) 上,然后做高维后缀和即可。

总时间复杂度 \(\mathcal{O}((n + 2^m + q) \times m)\)

code
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
constexpr int N = 23;
int n, m, q, f[1 << N], g[1 << N];
signed main() {
	ios :: sync_with_stdio(false);
	cin.tie(nullptr);
	cin >> m >> n >> q;
	for (int i = 1; i <= n; i++) {
		string s;
		cin >> s;
		s = ' ' + s;
		int x = 0, y = 0;
		for (int j = 1; j <= m; j++) {
			x *= 2, y *= 2;
			if (s[j] != '0') x += 1;
			if (s[j] == '1') y += 1; 
		}
		g[x] |= y;
	}
	for (int s = (1 << m) - 1; s >= 1; s--) {
		for (int i = 0; i < m; i++) {
			if (((s >> i) & 1) == 0) g[s] |= g[s | (1 << i)];
		}
	}
	for (int s = 1; s < (1 << m); s++) {
		if (s & g[s]) f[s] = f[s ^ (s & g[s])];
		else f[s] = s;
	}
	while (q--) {
		int x = 0; string s;
		cin >> s;
		s = ' ' + s;
		for (int i = 1; i <= m; i++) {
			x = x * 2;
			if (s[i] == '1') x += 1;
		}
		int cur = 0;
		for (int i = m - 1; i >= 0; i--) {
			if ((f[cur | (1 << i)] & x) == f[cur | (1 << i)]) cout << "1", cur |= (1 << i);
			else cout << "0";
		}
		cout << "\n";
	}
	return 0;
}

LG P7838 「Wdoi-3」夜雀 treating

给定一个长为 \(2n + 1\) 的序列 \(A\),集合 \(B\) 初始为空。你需要进行 \(n+1\) 次操作,每次操作会先将 \(A\) 中间的元素删除并扔到 \(B\) 里,然后再删除 \(A\) 中的一个元素。一种操作方案的权值定义为 \(B\) 中元素的值域最长连续段长度。求最大权值。\(n \leq 2 \times 10^5\)


转化一下题意:显然一开始就在 \(A\) 中间的一定会被取,剩下的操作相当于是给定两个长为 \(n\) 的栈,进行 \(n\) 次操作,每次操作删除其中一个栈的某个元素,然后将另一个栈的栈顶删除并扔到 \(B\) 里。

不妨先考察一下最后的 \(B\) 在原来的栈中可能分布成什么样子。我们钦定栈中的一些元素最后在 \(B\) 里,考虑如何判定是否存在一种满足条件的操作方式。不妨假设操作已经提前确定,即已经知道了选这些元素的时候删的是另一个栈中的哪些元素,把这些关系写成 \((p_i,q_i)\) 的形式,那么判定能否完成这些操作是简单的:能完成全部操作的充要条件是不存在某两个操作 \(i,j\) 满足 \(p_i < q_j\),且 \(p_j < q_i\)

必要性是显然的,充分性随便构造一下就行了。于是我们要解决的问题变成,给定 \(B\) 中的元素,我们要为它们找匹配,使得上面的情况尽可能不发生。感性地想,我们希望 \(q_i\) 尽量小,这样可能的 \(p_j\) 就会尽量少。同时,对于较小的 \(p_i\),由于可能的 \(q_j\) 更多,所以我们希望能够把较小的 \(q_i\) 分给它。于是我们的贪心策略是这样的:对于每个栈的关键元素,从栈低开始,依次匹配另一个栈最靠栈低的非关键元素。考虑直接枚举最长值域连续段,使用双指针,直接模拟判定过程可以得到 \(\mathcal{O}(n^2)\) 的时间复杂度。

考虑寻找更快的判定方式。设在 \(B\) 中的元素为 \(1\),其余元素为 \(0\),此时两个栈从栈低开始前缀 \(0\) 的个数分别为 \(L0_i,R0_i\),前缀 \(1\) 的个数分别为 \(L1_i,R1_i\),那么合法当且仅当不存在 \(i,j\) 使得 \(L1_i > R0_j\),且 \(R1_j > L0_i\)。考虑若这样的 \(i,j\) 存在,那么根据贪心匹配过程,一定会出现两组匹配 \((p1,q1),(p2,q2)\) 满足 \(p1\leq i,q1 > j\),且 \(p2 \leq j,q2 > i\),而这两组匹配矛盾。充分性也可以通过类似的分析得到。

但这个东西似乎没有办法快速维护。 事实上我们有更强的结论:合法当且仅当对于所有 \(i\),有 \(L1_i \leq R0_i\)。这是因为,若 \(L1_i > R0_j\),由于 \(1 \sim i\)\(0\)\(1\) 的总和固定,因此自然有 \(L0_i > R1_i\),反之亦然。于是可以直接用线段树维护 \(R0_i - L1_i\) 的最小值,每次只需要做一个区间加。于是可以直接用线段树维护 \(R0_i - L1_i\) 的最小值,每次只需要做一个区间加。

总时间复杂度 \(\mathcal{O}(n \log n)\)

code
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
constexpr int N = 4e5 + 5;
int n, a[N], q[N];
#define m ((l + r) >> 1)
int tr[N << 1], tg[N];
void ptag(int x, int v) { tr[x] += v, tg[x] += v; }
void down(int x) {
	if (tg[x]) ptag(x << 1, tg[x]), ptag(x << 1 | 1, tg[x]), tg[x] = 0;
}
void build(int x, int l, int r) {
	if (l == r) return tr[x] = l, void();
	build(x << 1, l, m), build(x << 1 | 1, m + 1, r);
	tr[x] = min(tr[x << 1], tr[x << 1 | 1]);
}
void mdf(int x, int l, int r, int ql, int qr, int v) {
	if (ql <= l && qr >= r) return ptag(x, v);
	down(x);
	if (ql <= m) mdf(x << 1, l, m, ql, qr, v);
	if (qr > m) mdf(x << 1 | 1, m + 1, r, ql, qr, v);
	tr[x] = min(tr[x << 1], tr[x << 1 | 1]);
}
#undef m
signed main() {
	ios :: sync_with_stdio(false);
	cin.tie(nullptr);
	cin >> n;
	for (int i = 1; i <= 2 * n + 1; i++) cin >> a[i], q[a[i]] = i;
	build(1, 1, n);
	int l = 1, r = 0, ans = 0;
	while (r <= 2 * n + 1) {
		if (tr[1] < 0) {
			int pos = q[l];
			if (pos == n + 1) { l += 1; continue; }
			if (pos <= n) mdf(1, 1, n, pos, n, 1);
			else mdf(1, 1, n, 2 * n + 1 - pos + 1, n, 1);
			l += 1;
		} else {
			ans = max(ans, r - l + 1);
			r += 1;
			int pos = q[r];
			if (pos == n + 1) continue;
			if (pos <= n) mdf(1, 1, n, pos, n, -1);
			else mdf(1, 1, n, 2 * n + 1 - pos + 1, n, -1);			
		}
	}
	cout << ans << "\n";
 	return 0;
}

LG P7735 [NOI2021] 轻重边

有一棵 \(n\) 个结点的树,初始时树上所有边都是轻边。维护 \(m\) 次操作,每次操作是以下两种之一:

  1. 给定两个点 \(a\)\(b\),对 \(a\)\(b\) 路径上的所有点 \(x\),将与 \(x\) 相连的所有边变为轻边。然后再将 \(a\)\(b\) 路径上包含的所有边变为重边。
  2. 给定两个点 \(a\)\(b\),求当前 \(a\)\(b\) 的路径上一共包含多少条重边。

\(T \leq 3\)\(n,m \leq 10^5\)


考虑一条边 \((u,v)\),它在某次操作 \(1\) 中被覆盖为了重边。如果在之后的某次操作 \(2\) 中,\(u\)\(v\) 被覆盖了而另一个点没有,那么这条边就会变成轻边。于是我们要支持的操作其实就是:链染色,查询链上满足 \(a_u=a_v\) 的边 \((u,v)\) 的数量。容易使用树剖线段树维护,每个节点只需要记录区间答案和左右端点的颜色即可。总时间复杂度 \(\mathcal{O}(n\log n+q\log^2 n)\)

code
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
constexpr int N = 1e5 + 5;
int n, m, tim, a[N]; vector <int> e[N];
int sz[N], dep[N], dfn[N], dfc, top[N], val[N], par[N], son[N];
void dfs(int u, int cur) {
	dep[u] = cur, sz[u] = 1, son[u] = 0;
	for (auto v : e[u]) if (v != par[u]) {
		par[v] = u;
		dfs(v, cur + 1), sz[u] += sz[v];
		if (sz[v] > sz[son[u]]) son[u] = v;
	}
}
void dfs1(int u, int anc) {
	val[dfn[u] = ++dfc] = a[u], top[u] = anc;
	if (son[u]) dfs1(son[u], anc);
	for (auto v : e[u]) if (v != par[u] && v != son[u]) {
		dfs1(v, v);
	}
}
struct SGT {
	#define m ((l + r) >> 1)
	int tr[N << 2], lc[N << 2], rc[N << 2], tg[N << 2];
	void up(int x) {
		tr[x] = tr[x << 1] + tr[x << 1 | 1] + (rc[x << 1] == lc[x << 1 | 1]);
		lc[x] = lc[x << 1];
		rc[x] = rc[x << 1 | 1];
	}
	void ptag(int x, int l, int r, int v) { tr[x] = r - l, lc[x] = rc[x] = tg[x] = v; }
 	void down(int x, int l, int r) {
		if (tg[x]) ptag(x << 1, l, m, tg[x]), ptag(x << 1 | 1, m + 1, r, tg[x]), tg[x] = 0;
	} 
	void build(int x, int l, int r) {
		tg[x] = 0;
		if (l == r) return tr[x] = 0, lc[x] = rc[x] = val[l], void();
		build(x << 1, l, m), build(x << 1 | 1, m + 1, r);
		up(x);
	}
	void mdf(int x, int l, int r, int ql, int qr, int v) {
		if (ql <= l && qr >= r) return ptag(x, l, r, v), void();
		down(x, l, r);
		if (ql <= m) mdf(x << 1, l, m, ql, qr, v);
		if (qr > m) mdf(x << 1 | 1, m + 1, r, ql, qr, v);
		up(x);
	}
	int qry(int x, int l, int r, int ql, int qr) {
		if (ql <= l && qr >= r) return tr[x];
		int ret = 0; down(x, l, r);
		if (ql <= m) ret += qry(x << 1, l, m, ql, qr);
		if (qr > m) ret += qry(x << 1 | 1, m + 1, r, ql, qr);
		if (ql <= m && qr > m) ret += (rc[x << 1] == lc[x << 1 | 1]); 
		return ret;
	}
	int get(int x, int l, int r, int p) {
		if (l == r) return lc[x];
		down(x, l, r);
		if (p <= m) return get(x << 1, l, m, p);
		return get(x << 1 | 1, m + 1, r, p);
	}
	#undef m
} t;
void mdf(int x, int y) {
	++tim;
	while (top[x] != top[y]) {
		if (dep[top[x]] < dep[top[y]]) swap(x, y);
		t.mdf(1, 1, n, dfn[top[x]], dfn[x], tim);
		x = par[top[x]];
	}
	if (dep[x] > dep[y]) swap(x, y);
	t.mdf(1, 1, n, dfn[x], dfn[y], tim);
}
int qry(int x, int y) {
	int ret = 0;
	while (top[x] != top[y]) {
		if (dep[top[x]] < dep[top[y]]) swap(x, y);
		ret += t.qry(1, 1, n, dfn[top[x]], dfn[x]);
		ret += (t.get(1, 1, n, dfn[top[x]]) == t.get(1, 1, n, dfn[par[top[x]]]));
		x = par[top[x]];
	}
	if (dep[x] > dep[y]) swap(x, y);
	ret += t.qry(1, 1, n, dfn[x], dfn[y]);
	return ret;
}
void clr() {
	dfc = tim = 0;
	for (int i = 1; i <= n; i++) e[i].clear();
}
void solve() {
	cin >> n >> m;
	for (int i = 1, x, y; i < n; i++) {
		cin >> x >> y;
		e[x].push_back(y);
		e[y].push_back(x);
	}
	for (int i = 1; i <= n; i++) a[i] = ++tim;
	dfs(1, 1), dfs1(1, 1);
	t.build(1, 1, n);
	while (m--) {
		int ty, x, y; 
		cin >> ty >> x >> y;
		if (ty == 1) {
			mdf(x, y);
		} else {
			cout << qry(x, y) << "\n"; 
		}
	}
	clr();
}
signed main() {
	ios :: sync_with_stdio(false);
	cin.tie(nullptr);
	int t; cin >> t;
	while (t--) solve();
 	return 0;
}

LG P8978 「DTOI-4」中位数

给定一个长度为 \(n\) 的整数序列 \(a\),你可以进行以下操作不超过 \(k\) 次:

  • 选择一个区间 \([l, r]\) 满足 \(1 \leq l \leq r \leq n\),并将 \([l, r]\) 中的所有数替换为这个区间的中位数。

求操作后 \(a\) 的最小值的最大值。对于一个长度为 \(L\) 的序列,其中位数定义为该序列中第 \(\lceil \frac{L}{2} \rceil\) 小的数。\(n \leq 10^4\)


虽然样例明示了,但我们首先二分答案,转化为 \(01\) 序列,目标是让所有数全为 \(1\)

对于题目给出的操作,我们有一些比较简单的观察:每次操作所选的区间一定是极大的。由此有一个简单的推论,如果有解,由于每次倍增,那么有操作次数 \(c \leq \lceil \log_2 n \rceil\)。设最优的操作序列为 \(I_1,I_2,\cdots,I_e\)

关键性质:对于任意 \(1 \leq i < j < e\)\(I_i \subseteq I_j\)\(I_j \subset I_i\)

证明

先证明相交必然包含:这点比较显然,考虑两次相交的操作 \(I_i = [a,c]\)\(I_j = [b,d]\),由于在 \(I_i\) 操作之后 \([a,b)\) 全是 \(1\),所以让 \(I_j\) 直接包过去,即令 \(I_j = [a,d]\) 一定合法。

接下来只需要证明一定相交。我们使用调整法。

找到最后两个相邻且不交的区间 \(I_j,I_{j+1}\),则 \(I_{j+1} \subset I_{j+2} \subset \cdots \subset I_e\)。因为 \(I_e = [1,n]\),所以一定存在 \(j+1 \leq p < e\) 使得 \(I_p \cap I_j = \varnothing\),且 \(I_{p+1} \cap I_j = I_j\)

不妨假设 \(|I_j| \geq |I_p|\),那么我们可以把 \(|I_j| \sim |I_p|\) 全部取消,替换成 \(I_j\) 的一个极大区间 \(I\),满足 \(I_j \subset I \subseteq I_{p+1}\),这样消去 \(0\) 的数量为 \(|I_j| - 1 \geq |I_p| - 1\),而 \(I_{j+1} \sim I_p\) 消去的 \(0\) 的数量显然不会超过 \(I_p-2\),因此替换后 \(I_{p+1}\)\(1\) 的数量一定增加,而操作数不增。

\(|I_j| < |I_p|\) 时同理。调整后,相邻且不交的区间的位置递减,因此调整总可以结束。故结论成立。

至此,我们可以设计 DP \(f_{i,l,r}\) 表示 \(I_i = [l,r]\) 时是否有救。暴力枚举区间转移,单次 check 时间复杂度 \(\mathcal{O}(n^4 \log n)\)

根据操作区间的极大性,因此对于每个 \(l\),我们只关心最大的 \(r\) 使得 \(f_{i,l,r}=1\)。考虑将定义域和值域互换,设 \(f_{i,l}\) 表示从 \(l\) 开始,最大的 \(r\) 使得原 \(f_{i,l,r} =1\)。再由极大性,若 \(p<q\)\(f_{i,p} \geq f_{i,q}\),那么 \(f_{i,q}\) 就没用了。因此,从 \(f_{i-1} \to f_i\) 时,我们只关心所有不被包含的 \(I_l = [l,f_{i,l}]\) 区间,它们的左右端点单调递增。

\(1\) 的权值为 \(1\)\(0\) 的权值为 \(-1\),前缀和序列为 \(s\),那么每次可以将权值为正的一段区间赋为 \(1\)。设 \(c(I)\) 表示操作 \(I = [l,r]\) 后对包含 \(I\) 的区间所产生的贡献,即 \(r - l+1 - (s_r-s_{l-1})\)。考虑检查 \(f_{i,l}\) 能否为 \(p\),那么我们求出所有使得 \([j,f_{i-1,j}] \subseteq [l,p]\)\(c([j,f_{i-1,j}])\) 的最大值 \(c_{\max}\),那么 \(s_p - s_{l-1}\) 加上 \(c_{\max}\) 之后应为正数,即满足 \(s_{l-1}-c_{\max} < s_p\)

利用单调性,使用扫描线 + 单调栈 + 线段树二分可以做到单次 check \(\mathcal{O}(n \log^2n)\),但还是无法通过。

考虑进一步利用递增性质,从后往前扫描线,那么加入区间 \(c(I_l)\) 时,权值小于 \(c(I_l)\) 的区间就没用了。这启发我们使用单调队列,从队头到队尾区间位置从右往左,同时权值递减。我们希望找到队列中第一个位置 \(I_p\),使得存在 \(q \geq f_{i-1,p}\)\(s_{l-1} - c(I_p) < s_q\)。预处理出 \(g_i\) 表示使得 \(s_q \geq i\) 的最大的 \(q\),则相当于检查是否有 \(g(s_{l-1} - c(I_p) + 1) \geq f_{i-1,p}\)

\(g\) 显然单调,而由于 \(c(I_p)\) 递减,\(-c(I_p)+1\) 同样单调增,问题在于 \(s_{l-1}\)。考虑若 \(p<q\)\(s_p \leq s_q\),则 \(q\) 一定不会作为左端点。丢掉这样的位置后,\(s_{l-1}\)\(l\) 递减而单调递增,故 \(s_{l-1}-c(I_p)+1\) 递增,可用单调队列维护。

总时间复杂度 \(\mathcal{O}(n \log^2 n)\)

code
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
constexpr int N = 4e5 + 5;
int n, k, a[N], b[N], s[N];
int f[N], tag[N], buc[N << 1];
bool check(int x) {
	for (int i = 1; i <= n; i++) {
		s[i] = s[i - 1] + (a[i] >= x ? 1 : -1);
	}
	memset(tag, 0, sizeof(tag));
	for (int i = 1, lim = N; i <= n; i++) {
		if (s[i - 1] < lim) lim = s[i - 1], tag[i] = 1;
	}
	memset(buc, 0, n + 2 << 3);
	for (int i = 1; i <= n; i++) buc[n + s[i]] = i;
	for (int i = n * 2 - 1; i >= 0; i--) buc[i] = max(buc[i], buc[i + 1]);
	for (int i = 1; i <= n; i++) f[i] = i - 1;
	for (int c = 1; c <= k && (1 << c - 1) <= n; c++) {
		static int d[N], val[N], hd, tl;
		hd = 1, tl = 0;
		for (int i = n; i >= 1; i--) {
			if (!tag[i]) continue;
			int v = f[i] - i + 1 - (s[f[i]] - s[i - 1]);
			while (hd <= tl && v >= val[tl]) tl--;
			val[++tl] = v, d[tl] = f[i];
			while (hd <= tl) {
				int v = val[hd], p = buc[max(0, s[i - 1] + 1 - v + n)];
				if (p >= d[hd]) { f[i] = p; break; }
				hd++;
			}
		}
		if (f[1] == n) return c <= k;
	}
	return 0;
}	
signed main() {
	ios :: sync_with_stdio(false);
	cin.tie(nullptr);
	cin >> n >> k;
	for (int i = 1; i <= n; i++) cin >> a[i], b[i] = a[i];
	sort(b + 1, b + n + 1);
	int c = unique(b + 1, b + n + 1) - b - 1;
	int l = 1, r = c, ans;
	while (l <= r) {
		int m = (l + r) >> 1;
		if (check(b[m])) l = m + 1, ans = b[m];
		else r = m - 1;
	}
	cout << ans << "\n";
 	return 0;
}

LG P7737 [NOI2021] 庆典

给定一个 \(n\) 个点 \(m\) 条边的弱连通有向图,满足若 \(x \Rightarrow z\)\(y \Rightarrow z\),则有 \(x \Rightarrow y\)\(y \Rightarrow x\)。其中 \(x \Rightarrow y\) 表示 \(x\) 可达 \(y\)

\(q\) 次询问,每次建 \(k\) 条临时的、可能不满足性质的单向边,然后询问从 \(x_i\) 走到 \(y_i\),有多少个点可能被经过。
\(n,q \leq 3 \times 10^5\)\(m \leq 6 \times 10^5\)\(0 \leq k \leq 2\)


缩点不影响可达性,我们先缩点。

假设缩点之后有两条边 \(y\to x,z \to x\),那么根据题目给出的性质,要么 \(y\) 能到 \(z\),要么 \(z\) 能到 \(y\)。不妨设 \(y\) 能到 \(z\),那么此时删去 \(y \to x\) 一定不影响连通性。因此,对于每个点,我们只需要保留起点拓扑序最后的入边即可。

于是我们要解决的就变成了树上问题。注意到,对于一段路径,如果其走法唯一,那么我们就可以把它缩成一条边,一起计算上面所有点的贡献。具体来说,我们将 \(s_i,t_i\) 和所有新边的端点拿出来建虚树,从祖先到后代连单向边,同时点带点权为其代表的强连通分量的点数,边带边权为两点之间不含端点的点权和。对于新加的边,我们在虚树上多连接一个边权为 \(0\) 的边。

沿用暴力做法:建原图和反图分别求出 \(s_i,t_i\) 能到达的点边,取个交就是答案。时间复杂度 \(\mathcal{O}((n+qk)\log n)\)。被卡常了。

code
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
constexpr int N = 3e5 + 5, M = 30;
int n, m, q, k, d[N];
inline int read() {
	int x = 0, w = 1; char ch = getchar();
	while (ch > '9' || ch < '0') { if (ch == '-') w = -1; ch = getchar(); }
	while (ch >= '0' && ch <= '9') x = x * 10 + ch - '0', ch = getchar();
	return x * w;
}
struct big_gragh {
	int hd[N], t;
	struct E { int to, nxt; } e[N * 2];
	void add(int u, int v) {
		e[++t].to = v, e[t].nxt = hd[u], hd[u] = t;
	}
} G1, G2;
struct small_graph {
	int hd[M], t;
	struct E { int to, nxt, val; } e[M * 2];
	void add(int u, int v, int w) {
		e[++t].to = v, e[t].nxt = hd[u], e[t].val = w, hd[u] = t;
	}
	void clr() {
		t = 0;
		memset(hd, 0, sizeof(hd));
	}
} G3, G4;
struct E {
	int to, nxt;
} e[N * 2];
int hd[N], t;
void add(int u, int v) {
	e[++t].to = v, e[t].nxt = hd[u], hd[u] = t;
}
int dfn[N], low[N], dfc, stk[N], tp, instk[N], sz[N], bcnt, col[N], ind[N];
void tarjan(int u) {
	dfn[u] = low[u] = ++dfc;
	instk[stk[++tp] = u] = 1;
	for (int i = hd[u]; i; i = e[i].nxt) {
		int v = e[i].to;
		if (!dfn[v]) {
			tarjan(v);
			low[u] = min(low[u], low[v]);
		} else {
			if (instk[v]) low[u] = min(low[u], dfn[v]);
		}
	}
	if (low[u] == dfn[u]) {
		bcnt++;
		int x = -1;
		while (x != u) {
			x = stk[tp--];
			instk[x] = 0;
			sz[bcnt]++;
			col[x] = bcnt;
		}
	}
}
void toposort() {
	queue <int> q;
	for (int i = 1; i <= bcnt; i++) if (d[i] == 0) q.push(i);
	while (!q.empty()) {
		int u = q.front(); q.pop();
		for (int i = G1.hd[u]; i; i = G1.e[i].nxt) {
			int v = G1.e[i].to;
			if (--d[v] == 0) {
//				cerr << "G2 add : " << u << " " << v << "\n";
				G2.add(u, v), q.push(v), ind[v]++;
			}
		}
	}
}
struct hash {
	unordered_map <int, int> mp;
	int all;
	void clr() { mp.clear(), all = 0; }
	int get(int x) {
		if (mp[x] == 0) mp[x] = ++all;
		return mp[x];
	}
} H;
int p[N][20], st[N], tim, dep[N], sum[N], lg[N];
void dfs(int u, int ff) {
	st[u] = ++tim, sum[u] = sum[ff] + sz[u];
	dep[u] = dep[p[u][0] = ff] + 1;
	for (int i = 1; i <= lg[dep[u]]; i++) p[u][i] = p[p[u][i - 1]][i - 1];
	for (int i = G2.hd[u]; i; i = G2.e[i].nxt) {
		int v = G2.e[i].to;
		if (v != ff) dfs(v, u);
	}
}
int lca(int x, int y) {
	if (dep[x] < dep[y]) swap(x, y);
	while (dep[x] > dep[y]) x = p[x][lg[dep[x] - dep[y]] - 1];
	if (x == y) return x;
	for (int i = lg[dep[x]]; i >= 0; i--) if (p[x][i] != p[y][i]) x = p[x][i], y = p[y][i];
	return p[x][0];
}
int vis1[M], vis2[M], mark[M], val[M];
void clr() {
	memset(vis1, 0, sizeof(vis1));
	memset(vis2, 0, sizeof(vis2));
	memset(mark, 0, sizeof(mark));
	memset(val, 0, sizeof(val));
}
int solve(int x, int y) {
	int ret = 0;
	queue <int> q;
	q.push(H.get(x));
	while (!q.empty()) {
		int u = q.front(); q.pop();
		mark[u] = 1;
		for (int i = G3.hd[u]; i; i = G3.e[i].nxt) {
			if (vis1[i] == 0) {
				vis1[i] = 1, q.push(G3.e[i].to);
 			} 
		}
	}
	q.push(H.get(y));
	while (!q.empty()) {
		int u = q.front(); q.pop();
		if (mark[u]) ret += val[u], mark[u] = 0;
		for (int i = G4.hd[u]; i; i = G4.e[i].nxt) {
			if (vis2[i] == 0) {
				vis2[i] = 1;
				if (vis1[i] == 1) ret += G4.e[i].val, vis1[i] = 0;
				q.push(G4.e[i].to);
			}
		}
  	}
	clr();
	return ret;
}
bool cmp(int i, int j) {
	return st[i] < st[j];
}
signed main() {
//	ios :: sync_with_stdio(false);
//	cin.tie(nullptr), cout.tie(nullptr);
	cin >> n >> m >> q >> k;
	for (int i = 1; i <= n; i++) lg[i] = lg[i >> 1] + 1;
	for (int i = 1, x, y; i <= m; i++) {
		x = read(), y = read(); add(x, y);
	}
	for (int i = 1; i <= n; i++) if (!dfn[i]) tarjan(i);
//	for (int i = 1; i <= n; i++) cerr << col[i] << " \n"[i == n];
	for (int u = 1; u <= n; u++) {
		for (int i = hd[u]; i; i = e[i].nxt) {
			int v = e[i].to;
			if (col[u] != col[v]) G1.add(col[u], col[v]), d[col[v]]++;
		}
	}
	toposort();
	int root = 0;
	for (int i = 1; i <= bcnt; i++) if (ind[i] == 0) root = i;
//	cerr << root << "\n";
	dfs(root, 0);
//	for (int i = 1; i <= bcnt; i++) cerr << sz[i] << " \n"[i == bcnt];
//	for (int i = 1; i <= bcnt; i++) cerr << sum[i] << " \n"[i == bcnt];
	for (int i = 1, x, y; i <= q; i++) {
		static int que[30], c;
		G3.clr(), G4.clr(), H.clr(), c = 0;
		x = read(), y = read();
		x = col[x];
		y = col[y];
		que[1] = x, que[2] = y;
		for (int j = 1, u, v; j <= k; j++) {
			u = read(), v = read();
			u = col[u];
			v = col[v];
			if (u == v) continue;
//			cerr << "add :" << H.get(u) << " " << H.get(v) << " " << 0 << "\n";
			G3.add(H.get(u), H.get(v), 0);
			G4.add(H.get(v), H.get(u), 0);
			que[j * 2 + 1] = u;
			que[j * 2 + 2] = v;
		}
		sort(que + 1, que + 2 * (k + 1) + 1, cmp);
		c = unique(que + 1, que + 2 * (k + 1) + 1) - que - 1;
		for (int j = 2; j <= c; j++) {
			int r = lca(que[j], que[j - 1]);
			if (r != que[j - 1] && r != que[j]) que[++c] = r;
		}
		sort(que + 1, que + c + 1, cmp);
		c = unique(que + 1, que + c + 1) - que - 1;
		for (int j = 1; j <= c; j++) val[H.get(que[j])] = sz[que[j]];	
		for (int j = 2; j <= c; j++) {
			int r = lca(que[j], que[j - 1]), v = sum[p[que[j]][0]] - sum[r];
			val[H.get(r)] = sz[r];
//			cerr << "add : " << H.get(r) << " " << H.get(que[j]) << " " << v << "\n";
			G3.add(H.get(r), H.get(que[j]), v);
			G4.add(H.get(que[j]), H.get(r), v);
		}
		cout << solve(x, y) << "\n";
	}
 	return 0;
}
posted @ 2023-06-24 20:51  came11ia  阅读(50)  评论(0编辑  收藏  举报