【AGC005D】~K Perm Counting

題面

洛谷

題解

將這個排列放到一個\(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; 
} 
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章