【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  
#include  
#include  
#include  
#include  
#include  
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; 
} 

你可能感兴趣的:(【AGC005D】~K Perm Counting)