題面
題解
將這個排列放到一個\(n\times n\)的棋盤上,那麼一個排列問題可以轉化爲每行每列只填一個數的放置方法問題。
對於這題的限制,我們將一列不能放的位置塗黑,那麼每一列就會有\(1\)至\(2\)個地方不能填(塗黑)。
考慮容斥這個東西,那麼就是用至少\(i\)列塗黑格來容斥:
\[ Ans=\sum _{i=0}^n (-1)^i(n-i)!f_i \]
因爲這裏主要是\(f_i\),也就是\(i\)列塗黑格的總方案數不好算,所以我們考慮怎麼算這個\(f\)。
發現如果兩個黑格在同一行或同一列他們就會衝突,我們將每一個點和與他同行、同列的點連起來,就會得到\(K+1\)條鏈,
而這很多條鏈中相鄰的點不能同時選,那麼你把放置的方案數dp出來即可。
代碼
#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <cmath>
#include <algorithm>
using namespace std;
const int Mod = 924844033;
const int MAX_N = 4e3 + 5;
int N = 4e3, K, fac[MAX_N];
int len[MAX_N], tot, cnt;
int f[MAX_N][MAX_N][2];
int main () {
fac[0] = 1; for (int i = 1; i <= N; i++) fac[i] = 1ll * fac[i - 1] * i % Mod;
cin >> N >> K;
for (int i = 1; i <= N; i++) {
int l = i + K, r = i - K;
if (l <= N && r >= 1) len[l] = len[r] + 2, len[r] = 0;
else if (l <= N && l >= 1) len[l]++;
else if (r <= N && r >= 1) len[r]++;
}
for (int i = 1; i <= N; i++) cnt += len[i], tot += (len[i] + 1) / 2;
f[0][0][0] = 1;
for (int i = 1, cur = 0; i <= N; i++) {
for (int j = cur + 1; j <= cur + len[i]; j++) {
for (int k = 0; k <= tot; k++) {
f[j][k][0] = (f[j - 1][k][1] + f[j - 1][k][0]) % Mod;
if (k) f[j][k][1] = f[j - 1][k - 1][0];
if (j == cur + 1 && k) f[j][k][1] = (f[j - 1][k - 1][1] + f[j][k][1]) % Mod;
}
}
cur += len[i];
}
int ans = 0;
for (int i = 0, op = 1; i <= tot; i++, op = Mod - op) {
int res = (f[cnt][i][0] + f[cnt][i][1]) % Mod;
ans = (ans + 1ll * op * res % Mod * fac[N - i]) % Mod;
}
printf("%d\n", ans);
return 0;
}