题解 - ARC118E Avoid Permutations

不太会容斥原理,选择一个和组合数学基本没啥关系的直接 DP 方法。

简述题意

定义排列 aa 权值为:在 (n+2)×(n+2)(n+2)\times(n+2) 网格图上删掉所有点 (i,ai)(i,a_i),每步只能从 (i,j)(i,j) 走到 (i+1,j)(i+1,j)(i,j+1)(i,j+1),从 (0,0)(0,0) 走到 (n+1,n+1)(n+1,n+1) 的方案数。现在把 aa 的若干位隐藏为 1-1,求所有可能的 aa 的权值和。n200n\le 200

分析

首先假设完全观察不到容斥,才来暴力刻画 DP 状态。

考虑以路径为主体,只关心这个路径要走到的下一个点有没有被删掉。而每行、每列(除了 00n+1n+1)都恰好有一个点被删掉。

假设当前路径已经走到 (x,y)(x,y),那么我们只考虑子矩形 [0,x]×[0,y][0,x]\times[0,y] 的总和。每当路径走到 (x,y+1)(x,y+1)(x+1,y)(x+1,y),都相当于是扩充了一行或一列,这扩充的部分可能有一个被删掉的点,也可能没有。这个删掉的点可能是某个 1-1 的决策,也可能是某个既定的决策。但是走到的那个点不可以被删掉,删掉的那个点要么在它下面,要么在它上面(或要么在它左边,要么在它右边)。基于这样的想法,我们设计状态来刻画。

fx,y,c,a,b(a,b{0,1})f_{x,y,c,a,b}(a,b\in \{0,1\}) 意为路径走到了 (x,y)(x,y),所有合法的状态数。状态是路径和填数方案的总和,填数方案要满足:在子矩形 [0,x]×[0,y][0,x]\times[0,y] 中,有 cc1-1 满足 ai>ya_i>yaa 指示 (x,y)(x,y) 左边是否有点被删掉,bb 指示下边。

考虑顺推转移。初始状态是 f0,0,0,0,0=1f_{0,0,0,0,0}=1

对于向上的转移,如果新的一行删掉的点不在左边,需要满足以下之一:

  • x=n+1x=n+1
  • 假设 bbaa 的逆排列(未出现设为 1-1),by+1>xb_{y+1}>x
  • by+1=1b_{y+1}=-1

转移系数为 11

如果新的一行删掉的点在左边:

  • 如果有一个既定的点在右边,或者当前在第 n+1n+1 行,不可以转移;
  • 如果有一个既定的点在左边,那直接转移;
  • 否则要考虑这个系数,就是在 (x,y)(x,y) 的左边选择一个 1-1 填这个值,是 c1c-1cc(因为第 xx 列自己可能被计入 cc)。

对于向右的转移,如果新的一列删掉的点不在下面,也是要满足三个条件之一,和上面向上转移不考虑删数的对称。

如果在下面,那么这一位如果有既定的决策也是直接以 0011 为转移系数。否则,就要计算 (x,y)(x,y) 下方有多少个还未被填的值,这个可以推出。具体来说,令 nci\textrm{nc}_i 表示有多少个 j<ij<i 满足 aj=1a_j=-1,令 hci\textrm{hc}_i 表示有多少个 j<ij<i 满足 jj 不存在于输入的 aa,那么这个系数就是 hcy(ncic)\textrm{hc}_y-(\textrm{nc}_i-c),视情况加一(因为第 yy 行左侧可能有已经确定的 1-1 未被去除影响)。

写转移的时候还要留意一下 cc 的变化。

最终答案就是 fn+1,n+1,0,0,0f_{n+1,n+1,0,0,0}

代码

细节真的不少。如果会容斥原理还是不要不切实际地写这种东西。

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
const int MXN = 205;
const int DVS = 998244353;
int N, nc[MXN], hc[MXN], arr[MXN], rev[MXN], cnt[MXN][MXN][MXN][2][2];

inline void add(int& x, int y) {
if ((x += y) >= DVS) x -= DVS;
}

int main() {
cin >> N;
memset(arr, -1, sizeof arr);
memset(rev, -1, sizeof rev);
for (int i(1); i <= N; ++i)
cin >> arr[i], rev[arr[i]] = i;
for (int i(2); i <= N + 1; ++i)
nc[i] = nc[i - 1] + (arr[i - 1] == -1),
hc[i] = hc[i - 1] + (rev[i - 1] == -1);
cnt[0][0][0][0][0] = 1;
for (int x(0); x <= N + 1; ++x) {
for (int y(0); y <= N + 1; ++y) {
for (int c(0); c <= x; ++c) {
for (int a(0); a <= 1; ++a) {
for (int b(0); b <= 1; ++b) {
if (!cnt[x][y][c][a][b]) continue;
if (x + 1 <= N + 1) {
int coe(0);
if (x + 1 == N + 1 || arr[x + 1] >= y) coe = 0;
else if (~arr[x + 1] && arr[x + 1] < y) coe = 1;
else
coe = max(0, hc[y] - (nc[x + 1] - c - (a && rev[y] == -1)));
add(cnt[x + 1][y][c][a][1],
cnt[x][y][c][a][b] * 1LL * coe % DVS);
if (x + 1 == N + 1 || arr[x + 1] > y || arr[x + 1] == -1)
add(cnt[x + 1][y][c + (x != N && arr[x + 1] == -1)][a][0],
cnt[x][y][c][a][b]);
}
if (y + 1 <= N + 1) {
int coe(0);
if (y + 1 == N + 1 || rev[y + 1] >= x) coe = 0;
else if (~rev[y + 1] && rev[y + 1] < x) coe = 1;
else coe = max(0, c - (!b && arr[x] == -1 && x != N + 1));
add(cnt[x][y + 1][c - (rev[y + 1] == -1)][1][b],
cnt[x][y][c][a][b] * 1LL * coe % DVS);
if (y + 1 == N + 1 || rev[y + 1] > x || rev[y + 1] == -1)
add(cnt[x][y + 1][c][0][b],
cnt[x][y][c][a][b]);
}
}
}
}
}
}
cout << cnt[N + 1][N + 1][0][0][0] << endl;
return 0;
}

题解 - ARC118E Avoid Permutations
http://sunsetglow95.github.io/2024/09/28/sol-ARC118E/
作者
SunsetGlow95
发布于
2024年9月28日
许可协议