题解:CF2187F2 Al Fine (Counting Version)
析合树好。
置换节点编号使得 \(a_i=i\),则子树内的 DFS 序连续。\(b_{l\sim r}\) 可以成为一棵子树当且仅当 \(b_l=\min_{i=l}^r b_i\) 且 \(\max_{i=l}^r b_i-\min_{i=l}^r b_i=r-l\)。
容易得到一个区间 DP:\(f_{l,r}\) 表示 \([l,r]\) 内的点构成森林的方案数。转移时枚举 \(l\) 所在的树的对应区间 \([l,k]\),转移是简单的:
其中 \(\operatorname{valid}(l,r)\) 表示 \(b_{l\sim r}\) 为合法段。\(0\) 是根节点,所以最终答案为 \(f_{1,n}\)。
这是 \(\mathcal{O}(n^3)\) 的,感觉上很难优化。
排列上的值域连续段,考虑放到析合树上分析。
对于析合树上的每个节点 \(u\),维护 \(a_u\) 表示 \(u\) 对应段构成一棵树的方案数,\(b_u\) 表示构成森林的方案数。
对于单点,显然有 \(a_u=b_u=1\)。
对于递减的合点,显然 \(b_l\) 不是区间最小值,因此 \(a_u=0\)。而任意一段连续的儿子节点也不能合并成一棵树,因此 \(b_u=\prod b_v\)。
对于析点,首先观察到 \(b_l\) 同样不是区间最小值,否则右侧的点一定可以合成更大的点,于是 \(a_u=0\)。类似地,析点的任意一段连续的儿子节点也不能合并成一棵树,有 \(b_u=\prod b_v\)。
对于递增的合点,任意一段连续的儿子节点都可以合成一棵树,不太好分析。考虑比较精妙的一步:在子树开始的位置之前插入左括号,结束的位置之后插入右括号,那么把区间划分成树可以视作括号匹配。观察到跨儿子的括号匹配的左端点必然是某个儿子连续段的左端点,右端点同理。对这个结构做 DP,设 \(f_{i,j}\) 表示考虑了前 \(i\) 个儿子,有 \(j\) 个左括号没有匹配。考察第 \(i\) 个儿子 \(v_i\) 开头的左括号所匹配的右括号的位置,转移有两种:
- 右括号在当前儿子段内:可以匹配掉任意多个尚未匹配的左括号,段内划分成森林接到前面。枚举 \(0\leq k\leq j\),则\[b_{v_i}\times f_{i-1,j}\to f_{i,k} \]
- 右括号在后面的儿子段内:尚未匹配的左括号不能在此处匹配掉,段内必须是完整的一棵树:\[a_{v_i}\times f_{i-1,j}\to f_{i,j+1} \]
显然转移可以写成:
后缀和优化即可。
显然可以直接得出 \(b_u=f_{k,0}\)。计算 \(a_u\) 时,对 \(v_1\) 之外的儿子作上述 DP,用 DP 结果乘上 \(a_{v_1}\) 即可。
可以 \(\mathcal{O}(n^2)\) 暴力建析合树。暴力找出所有连续段,对于每个 \(l\) 记录 \(mxR_l\) 表示以 \(l\) 为左端点的连续段中右端点的最大值,同理记录 \(mnL_r\)。那么一个连续段 \([l,r]\) 是本原连续段的条件就是 \(\max_{i=l+1}^r mxR_i\leq r\) 且 \(\min_{i=l}^{r-1}mnL_i\geq l\)。
时间复杂度为 \(\mathcal{O}(n^2)\)。
主要代码
int tc, n, pos[MAXN], p[MAXN];
int mxR[MAXN], mnL[MAXN];
int top, stk[MAXN];
vector<tuple<int, int, int>> R[MAXN];
vector<int> T[MAXN << 1];
mint a[MAXN << 1], b[MAXN << 1];
mint dp[MAXN << 1], suf[MAXN << 1];
struct Seg {
int l, r, mn, mx;
};
vector<Seg> seg;
void dfs(int u) {
if (T[u].empty()) {
a[u] = b[u] = 1;
return;
}
for (int v : T[u]) dfs(v);
bool inc = true;
for (int i = 0; i < T[u].size() - 1; ++i) inc &= seg[T[u][i]].mx < seg[T[u][i + 1]].mn;
if (!inc) {
a[u] = 0;
b[u] = 1;
for (int v : T[u]) b[u] *= b[v];
} else {
auto calc = [&](int l, int r) {
fill(dp, dp + r - l + 2, 0);
dp[0] = 1;
for (int i = l; i <= r; ++i) {
int v = T[u][i], lim = i - l + 1;
suf[lim] = 0;
for (int j = lim - 1; j >= 0; --j) suf[j] = suf[j + 1] + dp[j];
for (int j = lim; j >= 0; --j) {
dp[j] = suf[j] * b[v];
if (j) dp[j] += dp[j - 1] * a[v];
}
}
return dp[0];
};
a[u] = a[T[u][0]] * calc(1, T[u].size() - 1);
b[u] = calc(0, T[u].size() - 1);
}
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
cin >> tc;
while (tc--) {
cin >> n;
for (int i = 1; i <= n; ++i) {
int x;
cin >> x;
pos[x] = i;
}
for (int i = 1; i <= n; ++i) {
int x;
cin >> x;
p[i] = pos[x];
}
fill(mxR + 1, mxR + n + 1, -inf);
fill(mnL + 1, mnL + n + 1, inf);
for (int l = 1; l <= n; ++l) {
R[l].clear();
int mn = inf, mx = -inf;
for (int r = l; r <= n; ++r) {
chkMin(mn, p[r]);
chkMax(mx, p[r]);
if (mx - mn == r - l) {
R[l].emplace_back(r, mn, mx);
chkMax(mxR[l], r);
chkMin(mnL[r], l);
}
}
}
seg.clear();
for (int l = 1; l <= n; ++l) {
if(R[l].empty()) continue;
vector<int> mn(n + 1, inf), mx(n + 1, -inf);
for (int i = l; i <= n; ++i) {
mn[i] = min(mn[i - 1], mnL[i]);
if (i > l) mx[i] = max(mx[i - 1], mxR[i]);
}
for (auto [r, mnv, mxv] : R[l])
if (mn[r - 1] >= l && mx[r] <= r)
seg.push_back({l, r, mnv, mxv});
}
for (int i = 0; i < seg.size(); ++i) T[i].clear();
sort(seg.begin(), seg.end(), [&](const Seg &lhs, const Seg &rhs) {
return lhs.l != rhs.l ? lhs.l < rhs.l : lhs.r > rhs.r;
});
top = 1;
stk[top] = 0;
for (int i = 1; i < seg.size(); ++i) {
auto [l, r, mn, mx] = seg[i];
while (top && seg[stk[top]].r < r) --top;
T[stk[top]].emplace_back(i);
stk[++top] = i;
}
dfs(0);
cout << b[0] << '\n';
}
return 0;
}

浙公网安备 33010602011771号