题意:给定k,求有多少个n的排列,满足对于任意i,|a[i]−i|≠k
n<=2000
比较巧妙的一道题,考试一直刚这道题结果把自己刚死了。。。
根据类似错排的推法&容斥,我们可以得到答案为:
ans=ans+f[i](n-i)!(-1)^i (i=0,1,2…n)
其中fi表示有i个不合法位置的方案数
我们考虑怎么求fi
我们可以构建一个二分图,从左边的i向右边的i+k,i-k连边,可以发现一个完美匹配对应着一个排列。fi就转换成了大小为i(边数)的完美匹配数。我们可以发现对于这个图而言,其实是由k条不相交的路径构成的,我们可以把这些路径连在一起,成一条链来进行DP,但是我们要注意的就是路径的连接点。如果是连接点的话,匹配边数是不会增加的。
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#define maxn 4010
#define ll long long
using namespace std;
const ll modd=924844033;
int n,k;
void file(){
freopen("C.in","r",stdin);
freopen("C.out","w",stdout);
}
int vis[maxn][2];
int dp[maxn][maxn][2];
ll f[maxn];
ll ans;
ll fac[maxn];
int tag[maxn];int tot;
int main(){
file();
scanf("%d%d",&n,&k);
for(int i=1;i<=n;i++){
for(int j=0;j<=1;j++){
if(!vis[i][j]){
int x=i,y=j;int len=0;
while(x<=n){
vis[x][y]=1;
x+=k;y^=1;
len++;
}
tot+=len;
tag[tot]=1;
}
}
}
dp[1][0][0]=1;//printf("%d\n",tot);
for(int i=1;i<=tot;i++){
for(int j=0;j<=i;j++){
dp[i+1][j][0]=(dp[i][j][0]+dp[i][j][1])%modd;//if(dp[i+1][j][0]<0)printf("1*\n");
if(!tag[i]){
dp[i+1][j+1][1]=dp[i][j][0];//if(dp[i][j][0]<0)printf("*\n");
}
}
}
for(int i=1;i<=n;i++){
// ll tp=(dp[tot][i][0]+dp[tot][i][1]);
f[i]=(ll)(dp[tot][i][0]+dp[tot][i][1])%modd;
// if(f[i]<0)printf("3*\n");
}
fac[0]=1;
for(int i=1;i<=n;i++){
fac[i]=fac[i-1]*(ll)i%modd;
// if(fac[i]<0)printf("4*\n");
}
ans=fac[n];
for(int i=1,j=-1;i<=n;i++,j=-j){
ll tp=f[i]*fac[n-i]%modd;//if(tp<0)printf("5*\n");
if(j==1)
ans=(ans+modd+tp)%modd;
else ans=(ans-tp+modd)%modd;
}
printf("%lld\n",ans);
return 0;
}