【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; 
} 
posted @ 2020-01-15 19:43  heyujun  阅读(170)  评论(0编辑  收藏  举报