P13694 [CEOI 2025] Splits 题解

Description

对于一个长度为 nn 的排列 p=p[0],p[1],p[2],,p[n1]p = p[0], p[1], p[2], \ldots, p[n - 1](包含数字 1,2,3,,n1, 2, 3, \ldots, n 的一个全排列),我们定义分割排列(split)为一个排列 qq,它可以通过以下过程得到:

  1. 选择两个数集
    A=i1,i2,,ikA = i_1, i_2, \ldots, i_k
    B=j1,j2,,jlB = j_1, j_2, \ldots, j_l
    满足:
    • AB=A \cap B = \emptyset
    • AB={0,1,2,,n1}A \cup B = \{0, 1, 2, \ldots, n - 1\}
    • i1<i2<<iki_1 < i_2 < \ldots < i_k
    • j1<j2<<jlj_1 < j_2 < \ldots < j_l
  2. qq 定义为:

    q=p[i1]p[i2]p[ik]p[j1]p[j2]p[jl]q = p[i_1]\, p[i_2] \ldots p[i_k]\, p[j_1]\, p[j_2] \ldots p[j_l]

进一步,我们定义 S(p)S(p) 为排列 pp 的所有分割排列的集合。

现在,给定一个整数 nn 和一个集合 TT,其中包含 mm 个长度为 nn 的排列。要求统计有多少个长度为 nn 的排列 pp 满足 TS(p)T \subseteq S(p)。由于答案可能很大,请将结果对 998244353998\,244\,353 取模。

1n,m3001\leq n,m\leq 300

Solution

首先这个条件等价于对于每个 ii,都只存在至多一个 jj,使得 posai,j>posai,j+1pos_{a_{i,j}}>pos_{a_{i,j+1}},令 cuti=jcut_i=j

那么如果得到了每个 cuticut_i,则可以直接设 fi,jf_{i,j} 表示已经确定了第一行 [1,i][1,i][cut1+1,j][cut_1+1,j] 的数的位置的方案数。转移可以做到 O(m)O(m),总复杂度为 O(n2m)O(n^2m)

考虑如果没得到 cuticut_i 怎么做。

这里有一个结论是暴搜前缀,直到得到每个 cuticut_i 的这样的前缀数量是 O(n2+nm)O(n^2+nm) 级别的。证明就考虑如果存在一个 ii 没得到 cuticut_i,则当前搜出来的前缀一定是 aia_i 的前缀,只有总共 O(nm)O(nm) 种。

考虑下一步就得到所有 cuticut_i 的情况。

如果当前得到至少一个 cuticut_i,则下一步只有两种选择,贡献也就是 O(nm)O(nm)。如果当前一个 cuticut_i 都没得到,则当前的前缀是所有 aia_i 的公共前缀,最多 O(n)O(n) 种,暴力枚举 nn 次总共是 O(n2)O(n^2)。加起来就是 O(n2+nm)O(n^2+nm)

搜出来 cuticut_i 再暴力 dp 即可,因为常数很小,所以能过。

时间复杂度:O(n4m+n3m2)O(n^4m+n^3m^2)

Code

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
#include <bits/stdc++.h>

#ifdef ORZXKR
#include "grader.cpp"
#endif

const int kMaxN = 305, kMod = 998244353;

int n, m, t, ans;
int a[kMaxN][kMaxN], ps[kMaxN][kMaxN], cnt[kMaxN], p[kMaxN], pos[kMaxN];
int gg[kMaxN][kMaxN][kMaxN][2];

int qpow(int bs, int64_t idx = kMod - 2) {
int ret = 1;
for (; idx; idx >>= 1, bs = (int64_t)bs * bs % kMod)
if (idx & 1)
ret = (int64_t)ret * bs % kMod;
return ret;
}

inline int add(int x, int y) { return (x + y >= kMod ? x + y - kMod : x + y); }
inline int sub(int x, int y) { return (x >= y ? x - y : x - y + kMod); }
inline void inc(int &x, int y) { (x += y) >= kMod ? x -= kMod : x; }
inline void dec(int &x, int y) { (x -= y) < 0 ? x += kMod : x; }

int check() {
int ret = 2;
for (int i = 1; i <= m; ++i) {
// int cnt = 0;
// for (int j = 1; j < n; ++j) cnt += (pos[a[i][j]] > pos[a[i][j + 1]]);
if (cnt[i] > 1) return 0;
if (cnt[i] == 0) ret = 1;
}
return ret;
}

void getcnt() {
static int f[kMaxN][kMaxN], cut[kMaxN];
if (t == n) return inc(ans, 1);
for (int i = 1; i <= m; ++i) {
cut[i] = 0;
for (int j = 1; j < n; ++j) {
int v1 = pos[a[i][j]], v2 = pos[a[i][j + 1]];
if (v1 && v2 && v1 > v2 || !v1 && v2) assert(!cut[i]), cut[i] = j;
}
assert(cut[i]);
}
memset(f, 0, sizeof(f));
int ss = 0, tt = cut[1];
for (int i = 1; i <= n; ++i) {
int v1 = pos[a[1][i]], v2 = pos[a[1][i + 1]];
if (v1 && !v2) {
if (i <= cut[1]) ss = i;
else tt = i;
}
}
if (!ss && pos[a[1][cut[1]]]) ss = cut[1];
// std::cerr << t << " : ";
// for (int i = 1; i <= t; ++i) std::cerr << p[i] << " \n"[i == t];
f[ss][tt] = 1;
// std::cerr << "shabi " << ss << ' ' << tt << ' ' << cut[1] << '\n';
for (int i = ss; i <= cut[1]; ++i) {
for (int j = tt; j <= n; ++j) {
if (!f[i][j]) continue;
int x = 0, y = 0;
bool fl1 = 0, fl2 = 0;
if (i < cut[1]) x = a[1][i + 1], fl1 = 1;
if (j < n) y = a[1][j + 1], fl2 = 1;
if (x) assert(!pos[x]);
if (y) assert(!pos[y]);
// std::cerr << "shabi " << i << ' ' << j << ' ' << x << ' ' << y << '\n';
for (int k = 2; k <= m; ++k) {
if (fl1) {
int p = ps[k][x], v = a[k][p - 1];
if (v && (ps[1][v] > i && ps[1][v] <= cut[1] || ps[1][v] > j)) fl1 = 0;
}
if (fl2) {
int p = ps[k][y], v = a[k][p - 1];
if (v && (ps[1][v] > i && ps[1][v] <= cut[1] || ps[1][v] > j)) fl2 = 0;
}
}
if (fl1) inc(f[i + 1][j], f[i][j]);
if (fl2) inc(f[i][j + 1], f[i][j]);
// if (fl2) std::cerr << "sbbb " << i << ' ' << j << ' ' << y << ' ' << ps[1][a[2][ps[2][y] - 1]] << '\n';
}
}
// std::cerr << "fuck " << f[cut[1]][n] << ' ' << cut[1] << ' ' << cut[2] << ' ' << cut[3] << '\n';
// std::cerr << "fuck " << cut[2] << ' ' << f[cut[1]][n] << ' ' << pos[1] << ' ' << pos[2] << ' ' << pos[3] << '\n';
inc(ans, f[cut[1]][n]);
}

void dfs() {
int fl = check();
if (!fl) return;
else if (t == n || fl == 2) return getcnt();
for (int i = 1; i <= n; ++i) {
if (pos[i]) continue;
p[++t] = i, pos[i] = t;
for (int j = 1; j <= m; ++j) cnt[j] += (ps[j][i] > 1 && !pos[a[j][ps[j][i] - 1]]);
dfs();
--t, pos[i] = 0;
for (int j = 1; j <= m; ++j) cnt[j] -= (ps[j][i] > 1 && !pos[a[j][ps[j][i] - 1]]);
}
}

int solve(int n, int m, std::vector<std::vector<int>>& splits) {
::n = n, ::m = m;
for (int i = 1; i <= m; ++i)
for (int j = 1; j <= n; ++j)
ps[i][a[i][j] = splits[i - 1][j - 1]] = j;
dfs();
// p[++t] = 2, pos[2] = 1;
// getcnt();
// p[++t] = 2, p[++t] = 3;
// pos[2] = 1, pos[3] = 2;
// getcnt();
return ans;
}

P13694 [CEOI 2025] Splits 题解
https://sobaliuziao.github.io/2025/09/16/post/f1d69434.html
作者
Egg_laying_master
发布于
2025年9月16日
许可协议