D - ~K Perm Counting
题意:
求有多少排列对于每个位置i都满足$|ai−i|!=k$。n<=2000
分析:
容斥+dp。
$answer = \sum\limits_{i = 0}^{n}(-1)^ig[i] \times (n - i)!$
$g[i]$表示至少存在I个位置满足$a[i] - i = k$个数。
考虑如何求出$g[]$。 如果建立两列点,一个表示数字,一个表示下标,左边第i个点与右边第i-k和i+k个点连边,那么这是一张二分图,g[i]就是求满足有刚好i个匹配的方案数。
发现这样图可以按照模k的余数分成2k条链,每条链互不影响,链内满足不能有相邻的一起选。于是可以$f[i][j][0]$表示到链上的第i个位置,当前有j个匹配,第i个选不选的方案数。
考虑如何将2k条链合并:因为链与链之间是互不影响的,所以可以建立一个点,连接两条链,然后让这个点一定不选即可。
代码:
#include<cstdio> #include<algorithm> #include<cstring> #include<iostream> #include<cmath> #include<cctype> #include<set> #include<queue> #include<vector> #include<map> using namespace std; typedef long long LL; inline int read() { int x=0,f=1;char ch=getchar();for(;!isdigit(ch);ch=getchar())if(ch=='-')f=-1; for(;isdigit(ch);ch=getchar())x=x*10+ch-'0';return x*f; } const int N = 4005, mod = 924844033; int f[N][N][2], g[N], A[N], fac[N]; inline void add(int &x,int y) { x += y; if (x >= mod) x -= y; } int main() { int n = read(), k = read(), Ans = 0; int cnt = 0; for (int i = 1; i <= k; ++i) { for (int j = i; j <= n; j += k) A[++cnt] = (i == j); for (int j = i; j <= n; j += k) A[++cnt] = (i == j); } f[0][0][0] = 1; for (int i = 1; i <= (n << 1); ++i) for (int j = 0; j <= i; ++j) { add(f[i][j][0], (f[i - 1][j][0] + f[i - 1][j][1]) % mod); if (!A[i] && j) add(f[i][j][1], f[i - 1][j - 1][0]); } fac[0] = 1; for (int i = 1; i <= n; ++i) fac[i] = 1ll * fac[i - 1] * i % mod; for (int i = 0; i <= n; ++i) { int res = (f[n << 1][i][0] + f[n << 1][i][1]) % mod; Ans += 1ll * (i & 1 ? -1 : 1) * res * fac[n - i] % mod; Ans = (Ans % mod + mod) % mod; } cout << Ans; return 0; }