AtCoder [AGC005D] ~K Perm Counting

题目描述

https://atcoder.jp/contests/agc005/tasks/agc005_d

简要题意:如果一个排列满足对于所有的 $i$,都有 $|P_i-i|\neq k$,则称排列 $P$ 合法,现在给定 $n$ 和 $k$​,求有多少合法排列

$2\le n\le 2000,1\le k\le n - 1$

Solution

考虑容斥,我们令 $f_i$ 表示至少有 $i$ 个位置满足 $|P_i-i|=k$,$g_i$ 表示恰好有 $i$ 个位置满足 $|P_i-i|=k$,所求即为 $g_0=\sum_{i=0}^n(-1)^if_i$​

我们考虑如何求 $f_i$,我们考虑原问题转化,考虑二分图左部点表示 $P$,右部点表示 $1$ 到 $n$,我们将左部点 $i$ 连向右部点 $i-k$ 和 $i+k$,容易发现,会影响的点是若干条链,根据组合数学经典式子,可以得到 $n$ 个点链,选择 $m$ 条互不相邻的边的方案数是 $\binom{n-m}{m}$,那么 $f_i$ 就是一个直接按照背包的做法合并即可

时间复杂度 $O(n^2)$,使用多项式快速幂可以做到 $O(n\log n)$

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
#include <iostream>
#define maxn 2010
#define ll long long
using namespace std;

const int p = 924844033;

ll pow_mod(ll x, ll n) {
ll s = 1;
for (; n; n >>= 1, x = x * x % p)
if (n & 1) s = s * x % p;
return s;
}

ll fac[maxn], inv[maxn];
void init_C(int n) {
fac[0] = 1; for (int i = 1; i <= n; ++i) fac[i] = fac[i - 1] * i % p;
inv[n] = pow_mod(fac[n], p - 2); for (int i = n - 1; ~i; --i) inv[i] = inv[i + 1] * (i + 1) % p;
}

ll C(int n, int m) { return n < m ? 0 : fac[n] * inv[m] % p * inv[n - m] % p; }

int n, k;

ll f[maxn * 2][maxn];
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr); cout.tie(nullptr);

cin >> n >> k; init_C(n); f[0][0] = 1;
for (int i = 1; i <= 2 * k; ++i) {
int t = (n - (i > k ? i - k : i) + k) / k;
for (int j = 0; j <= n; ++j)
for (int k = 0; k <= min(j, t); ++k)
f[i][j] = (f[i][j] + f[i - 1][j - k] * C(t - k, k)) % p;
} ll ans = 0;
for (int i = 0, opt = 1; i <= n; ++i, opt = -opt)
ans = (ans + opt * fac[n - i] * f[2 * k][i]) % p;
cout << (ans + p) % p << "\n";
return 0;
}