洛谷P13273 [NOI2025] 数字树 题解

司马只因锥 2026-6-20 15:23:02 17 浏览 1 点赞 1 收藏

题目保证存在一种合法方案,我们可以考虑由一个合法方案怎么调整出不同的合法方案。

为了方便理解,我们钦定优先向左遍历,把换向操作转换成翻转子树的操作。

容易发现,翻转一棵子树时已经合法匹配的点不受影响,那么只有那些当前未配对的点需要考虑。跟虚树的角度差不多,我们把这些子树内已经匹配上的点踢掉分析。

分析问题的重要方法:从小入手。

两种颜色

XXYY 子树内都有一个 AA 和一个 BB 颜色节点没有参与匹配,翻转点 XX 后应该翻转 YY 才能构成新的合法结构。

YY 子树内只有 A1A_1 没有匹配,可以随意翻转;而 XX 翻转后无法通过翻转其他节点构成合法结构。

可以发现,对于一个合法序列,翻转一部分,如果这个部分中至少有两个未匹配的点,那么必须将对应区间也翻转,或者在其他点把这个部分翻转回来。

那假如我们已经翻转了一个部分,到底翻转哪些点才能转回合法?

两个不够具有普适性,我们引入第三个节点。

三种颜色

翻转 YY 后,ZZWW 都可以把 A2,B2A_2,B_2 相对位置调整正确,然而这会使得 C2C_2 调到中间不再合法,因此只能翻转 WW

通过上述的分析,我们发现,翻转了一个点 uu,就必须翻转树上管辖着一模一样的 SSvv

至此我们可以得出结论:设 SuS_uuu 子树内未匹配节点集合。若 Su2|S_u| \geq 2,则当且仅当 Su=Sv (uv)S_u=S_v \ (u \neq v),可以通过同时翻转子树 u,vu, v 得出新的合法方案。若 Su=1|S_u|=1,那怎么样翻转都行。

怎么计数?设等价类 AkA_k 包含了所有 SS 相同,且 S2|S| \geq 2 的点,等价类共有 mm 个。设 S=1|S|=1 的点有 tt 个。每个等价类我们都可以任选偶数个翻转,则:

$$ans=2^t\prod_{k=1}^{m}\sum_{i=0}^{|A_k|/2} \binom{|A_k|}{2i}=2^t\prod_{k=1}^{m}2^{|A_k|-1}=2^{2n-1-m} $$

问题转化为计算等价类的个数。

ii 次操作加入颜色 ii,下标不仅是元素编号,也表示了时间,务必记住这一点。

用 01 序列表示集合 SuS_u。若两 01 序列的 lcp 长度为 ll,则说明在前 ll 次操作里它们都属于同一个等价类!

使用树上差分标记修改,显然可以用线段树高效维护合并集合。至此我们得到了每个点子树内的未匹配点集,我们把这些字符串记为 a[i]a[i]

已知所有集合,如何计数等价类?

对所有字符串按字典序排序后(比较操作就是查询 lcp,这可以通过对比两棵线段树上区间哈希值来实现,单次对比复杂度是 O(logn)O(\log n) 的),若 lcp(a[i1],a[i])=L\text{lcp}(a[i-1], a[i])=L,这意味着在 L+1L+1 时新出现了一个等价类。

由于更长的 lcp 覆盖了短的 lcp,如果字符串 $\text{lcp}(a[i], a[j]) \leq \text{lcp}(a[i], a[i-1])$,那么在 i1i-1 或更早,a[i],a[j]a[i], a[j] 分歧产生的新等价类已经被计数过了。所以这就覆盖了所有的情况,避免了冗余的比较。

Code

有点难写啊,空间有点玄学,瞎开的。

#include <bits/stdc++.h>
#define int int64_t
//#define int __int128
#define MOD (1000000007)
//#define eps (1e-6)
#define endl '\n'
#define debug_endl cout<<endl;
#define debug cout<<"debug"<<endl;
using namespace std;
const int MAXN = 2e6;
const int MAXB = 24;
int c, n;
int lc[MAXN], rc[MAXN];
int fa[25][MAXN], depth[MAXN], pw131[MAXN], ans[MAXN];
vector<int> add[MAXN], del[MAXN];
struct Mergeable_Segment_Tree {
	struct Node {
		int h, pos1, pos2;
		int lc, rc;
		Node() {
			h = lc = rc = 0;
			pos1 = pos2 = 1e9;
		}
	};
	Node tr[int(2e7)];
	int root[MAXN], tot;
	void push_up(int p, int l, int r) {
		int mid = (l + r) >> 1;
		tr[p].h = (tr[tr[p].lc].h * pw131[r - mid] + tr[tr[p].rc].h) % MOD;
		if (tr[tr[p].lc].pos1 == 1e9) {
			tr[p].pos1 = tr[tr[p].rc].pos1;
			tr[p].pos2 = tr[tr[p].rc].pos2;
		} else {
			tr[p].pos1 = tr[tr[p].lc].pos1;
			if (tr[tr[p].lc].pos2 != 1e9) tr[p].pos2 = tr[tr[p].lc].pos2;
			else tr[p].pos2 = tr[tr[p].rc].pos1;
		}
	}
	int update(int p, int l, int r, int pos, int k) {
		int q = ++tot;
		tr[q] = tr[p];
		if (l == r) {
			tr[q].h = k;
			tr[q].pos1 = (k ? l : 1e9);
			tr[q].pos2 = 1e9;
			return q;
		}
		int mid = (l + r) >> 1;
		if (pos <= mid) tr[q].lc = update(tr[p].lc, l, mid, pos, k);
		else tr[q].rc = update(tr[p].rc, mid + 1, r, pos, k);
		push_up(q, l, r);
		return q;
	}
	int merge(int p1, int p2, int l, int r) {
		if (!p1 || !p2) return p1 ? p1 : p2;
		int p = ++tot;
		if (l == r) {
			tr[p].h = (tr[p1].h + tr[p2].h) % MOD;
			tr[p].pos1 = min(tr[p1].pos1, tr[p2].pos1);
			tr[p].pos2 = 1e9;
			return p;
		}
		int mid = (l + r) >> 1;
		tr[p].lc = merge(tr[p1].lc, tr[p2].lc, l, mid);
		tr[p].rc = merge(tr[p1].rc, tr[p2].rc, mid + 1, r);
		push_up(p, l, r);
		return p;
	}
	pair<int, int> lcp(int p1, int p2, int l, int r) {
		if (tr[p1].h == tr[p2].h) return make_pair(n + 1, 1);
		if (!p1) return make_pair(tr[p2].pos1, 2);
		if (!p2) return make_pair(tr[p1].pos1, 1);
		if (l == r) return make_pair(l, !tr[p1].h ? 2 : 1);
		int mid = (l + r) >> 1;
		if (tr[tr[p1].lc].h != tr[tr[p2].lc].h) return lcp(tr[p1].lc, tr[p2].lc, l, mid);
		else return lcp(tr[p1].rc, tr[p2].rc, mid + 1, r);
	}
};
Mergeable_Segment_Tree mst;
void dfs(int x) {
	if (!x) return;
	for (int i = 1; i <= MAXB; ++i) {
		fa[i][x] = fa[i - 1][fa[i - 1][x]];
	}
	if (lc[x]) {
		fa[0][lc[x]] = x;
		depth[lc[x]] = depth[x] + 1;
		dfs(lc[x]);
	}
	if (rc[x]) {
		fa[0][rc[x]] = x;
		depth[rc[x]] = depth[x] + 1;
		dfs(rc[x]);
	}
}
inline int lca(int x, int y) {
	if (depth[x] != depth[y]) {
		if (depth[x] < depth[y]) {
			swap(x, y);
		}
		for (int i = MAXB; i >= 0; --i) {
			if (depth[fa[i][x]] >= depth[y]) {
				x = fa[i][x];
			}
		}
	}
	if (x == y) return x;
	for (int i = MAXB; i >= 0; --i) {
		if (fa[i][x] != fa[i][y]) {
			x = fa[i][x];
			y = fa[i][y];
		}
	}
	return fa[0][x];
}
void dfs2(int x) {
	if (!x) return;
	dfs2(lc[x]);
	dfs2(rc[x]);
	mst.root[x] = mst.merge(mst.root[x], mst.root[lc[x]], 1, n);
	mst.root[x] = mst.merge(mst.root[x], mst.root[rc[x]], 1, n);
	for (int d : del[x]) {
		mst.root[x] = mst.update(mst.root[x], 1, n, d, 0);
	}
	for (int d : add[x]) {
		mst.root[x] = mst.update(mst.root[x], 1, n, d, 1);
	}
}
bool cmp(int p1, int p2) {
	return mst.lcp(p1, p2, 1, n).second == 2;
}
inline int qpow(int a, int b) {
	int res = 1;
	while (b) {
		if (b & 1) res = (res * a) % MOD;
		a = (a * a) % MOD;
		b >>= 1;
	}
	return res;
}
signed main() {
	ios::sync_with_stdio(false);
	cin.tie(0), cout.tie(0);
	cin >> c >> n;
	depth[1] = 1;
	pw131[0] = 1;
	for (int i = 1; i <= n; ++i) {
		pw131[i] = (pw131[i - 1] * 131) % MOD;
	}
	for (int i = 1; i <= 2 * n - 1; ++i) {
		cin >> lc[i] >> rc[i];
	}
	dfs(1);
	for (int i = 1; i <= n; ++i) {
		int a, b;
		cin >> a >> b;
		int l = lca(a, b);
		add[a].emplace_back(i);
		add[b].emplace_back(i);
		del[l].emplace_back(i);
	}
	dfs2(1);
	sort(mst.root + 1, mst.root + 2 * n, cmp);
	for (int i = 1; i <= 2 * n - 1; ++i) {
		if (mst.tr[mst.root[i]].pos2 != 1e9) { //新等价类对应S集合的元素个数必须大于1
			if (i == 1) ++ans[mst.tr[mst.root[1]].pos2];
			else
				++ans[max(mst.lcp(mst.root[i], mst.root[i - 1], 1, n).first, mst.tr[mst.root[i]].pos2)];
		}
	}
	for (int i = 1; i <= n; ++i) {
		ans[i] += ans[i - 1];
		cout << qpow(2, 2 * n - 1 - ans[i]) << endl;
	}
	return 0;
}

评论

0 条
还没有评论。