题目保证存在一种合法方案,我们可以考虑由一个合法方案怎么调整出不同的合法方案。
为了方便理解,我们钦定优先向左遍历,把换向操作转换成翻转子树的操作。
容易发现,翻转一棵子树时已经合法匹配的点不受影响,那么只有那些当前未配对的点需要考虑。跟虚树的角度差不多,我们把这些子树内已经匹配上的点踢掉分析。
分析问题的重要方法:从小入手。
两种颜色
X X X 和 Y Y Y 子树内都有一个 A A A 和一个 B B B 颜色节点没有参与匹配,翻转点 X X X 后应该翻转 Y Y Y 才能构成新的合法结构。
Y Y Y 子树内只有 A 1 A_1 A 1 没有匹配,可以随意翻转;而 X X X 翻转后无法通过翻转其他节点构成合法结构。
可以发现,对于一个合法序列,翻转一部分,如果这个部分中至少有两个未匹配的点,那么必须将对应区间也翻转,或者在其他点把这个部分翻转回来。
那假如我们已经翻转了一个部分,到底翻转哪些点才能转回合法?
两个不够具有普适性,我们引入第三个节点。
三种颜色
翻转 Y Y Y 后,Z Z Z 和 W W W 都可以把 A 2 , B 2 A_2,B_2 A 2 , B 2 相对位置调整正确,然而这会使得 C 2 C_2 C 2 调到中间不再合法,因此只能翻转 W W W 。
通过上述的分析,我们发现,翻转了一个点 u u u ,就必须翻转树上管辖着一模一样的 S S S 的 v v v 。
至此我们可以得出结论:设 S u S_u S u 为 u u u 子树内未匹配节点集合。若 ∣ S u ∣ ≥ 2 |S_u| \geq 2 ∣ S u ∣ ≥ 2 ,则当且仅当 S u = S v ( u ≠ v ) S_u=S_v \ (u \neq v) S u = S v ( u = v ) ,可以通过同时翻转子树 u , v u, v u , v 得出新的合法方案。若 ∣ S u ∣ = 1 |S_u|=1 ∣ S u ∣ = 1 ,那怎么样翻转都行。
怎么计数?设等价类 A k A_k A k 包含了所有 S S S 相同,且 ∣ S ∣ ≥ 2 |S| \geq 2 ∣ S ∣ ≥ 2 的点,等价类共有 m m m 个。设 ∣ S ∣ = 1 |S|=1 ∣ S ∣ = 1 的点有 t t t 个。每个等价类我们都可以任选偶数个翻转,则:
$$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}
$$问题转化为计算等价类的个数。
第 i i i 次操作加入颜色 i i i ,下标不仅是元素编号,也表示了时间,务必记住这一点。
用 01 序列表示集合 S u S_u S u 。若两 01 序列的 lcp 长度为 l l l ,则说明在前 l l l 次操作里它们都属于同一个等价类!
使用树上差分标记修改,显然可以用线段树高效维护合并集合。至此我们得到了每个点子树内的未匹配点集,我们把这些字符串记为 a [ i ] a[i] a [ i ] 。
已知所有集合,如何计数等价类?
对所有字符串按字典序排序后(比较操作就是查询 lcp,这可以通过对比两棵线段树上区间哈希值来实现,单次对比复杂度是 O ( log n ) O(\log n) O ( log n ) 的),若 lcp ( a [ i − 1 ] , a [ i ] ) = L \text{lcp}(a[i-1], a[i])=L lcp ( a [ i − 1 ] , a [ i ]) = L ,这意味着在 L + 1 L+1 L + 1 时新出现了一个等价类。
由于更长的 lcp 覆盖了短的 lcp,如果字符串 $\text{lcp}(a[i], a[j]) \leq \text{lcp}(a[i], a[i-1])$,那么在 i − 1 i-1 i − 1 或更早,a [ i ] , a [ j ] 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
题目保证存在一种合法方案,我们可以考虑由一个合法方案怎么调整出不同的合法方案。
为了方便理解,我们钦定优先向左遍历,把换向操作转换成翻转子树的操作。
容易发现,翻转一棵子树时已经合法匹配的点不受影响,那么只有那些当前未配对的点需要考虑。跟虚树的角度差不多,我们把这些子树内已经匹配上的点踢掉分析。
分析问题的重要方法:从小入手。
两种颜色
X X X 和 Y Y Y 子树内都有一个 A A A 和一个 B B B 颜色节点没有参与匹配,翻转点 X X X 后应该翻转 Y Y Y 才能构成新的合法结构。
Y Y Y 子树内只有 A 1 A_1 A 1 没有匹配,可以随意翻转;而 X X X 翻转后无法通过翻转其他节点构成合法结构。
可以发现,对于一个合法序列,翻转一部分,如果这个部分中至少有两个未匹配的点,那么必须将对应区间也翻转,或者在其他点把这个部分翻转回来。
那假如我们已经翻转了一个部分,到底翻转哪些点才能转回合法?
两个不够具有普适性,我们引入第三个节点。
三种颜色
翻转 Y Y Y 后,Z Z Z 和 W W W 都可以把 A 2 , B 2 A_2,B_2 A 2 , B 2 相对位置调整正确,然而这会使得 C 2 C_2 C 2 调到中间不再合法,因此只能翻转 W W W 。
通过上述的分析,我们发现,翻转了一个点 u u u ,就必须翻转树上管辖着一模一样的 S S S 的 v v v 。
至此我们可以得出结论:设 S u S_u S u 为 u u u 子树内未匹配节点集合。若 ∣ S u ∣ ≥ 2 |S_u| \geq 2 ∣ S u ∣ ≥ 2 ,则当且仅当 S u = S v ( u ≠ v ) S_u=S_v \ (u \neq v) S u = S v ( u = v ) ,可以通过同时翻转子树 u , v u, v u , v 得出新的合法方案。若 ∣ S u ∣ = 1 |S_u|=1 ∣ S u ∣ = 1 ,那怎么样翻转都行。
怎么计数?设等价类 A k A_k A k 包含了所有 S S S 相同,且 ∣ S ∣ ≥ 2 |S| \geq 2 ∣ S ∣ ≥ 2 的点,等价类共有 m m m 个。设 ∣ S ∣ = 1 |S|=1 ∣ S ∣ = 1 的点有 t t t 个。每个等价类我们都可以任选偶数个翻转,则:
$$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}
$$问题转化为计算等价类的个数。
第 i i i 次操作加入颜色 i i i ,下标不仅是元素编号,也表示了时间,务必记住这一点。
用 01 序列表示集合 S u S_u S u 。若两 01 序列的 lcp 长度为 l l l ,则说明在前 l l l 次操作里它们都属于同一个等价类!
使用树上差分标记修改,显然可以用线段树高效维护合并集合。至此我们得到了每个点子树内的未匹配点集,我们把这些字符串记为 a [ i ] a[i] a [ i ] 。
已知所有集合,如何计数等价类?
对所有字符串按字典序排序后(比较操作就是查询 lcp,这可以通过对比两棵线段树上区间哈希值来实现,单次对比复杂度是 O ( log n ) O(\log n) O ( log n ) 的),若 lcp ( a [ i − 1 ] , a [ i ] ) = L \text{lcp}(a[i-1], a[i])=L lcp ( a [ i − 1 ] , a [ i ]) = L ,这意味着在 L + 1 L+1 L + 1 时新出现了一个等价类。
由于更长的 lcp 覆盖了短的 lcp,如果字符串 $\text{lcp}(a[i], a[j]) \leq \text{lcp}(a[i], a[i-1])$,那么在 i − 1 i-1 i − 1 或更早,a [ i ] , a [ j ] 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