Description

原题​​传送门​

定义一个排列P的第i个位置是好的,当且仅当|Pi−i|=1 | P i − i | = 1
给出n,m,要求长度为n的好的位置恰好有m个的排列数量。

1<=n<=1000,0<=m<=n 1 <= n <= 1000 , 0 <= m <= n

Solution

既然是恰好m个,我们第一反应肯定是容斥。

求出至少有多少个好的位置。

考虑DP,(不得不说这个状态设计真是精妙)

我们先假定不考虑它具体是怎么排列的,按位置填数。
第i个位置是好的,那就要么它放了i+1,要么它放了i-1

设F[i][j][0/1][0/1] F [ i ] [ j ] [ 0 / 1 ] [ 0 / 1 ] 表示当前放到第i个位置,已经确定有j个位置是好的,数i是否已用,数i+1是否已用。
枚举下一个是否让他成为好的位置,分情况讨论即可。
注意如果不是好的位置,我们管它具体选什么,直接将他当做任意的数来转移即可。

最后令G[i] G [ i ] 表示至少有i个位置的答案,它明显等于f[n][i][0][0]+f[n][i][1][0] f [ n ] [ i ] [ 0 ] [ 0 ] + f [ n ] [ i ] [ 1 ] [ 0 ] (不存在n+1这个数)

总的答案就是∑j=mnG[j]∗(n−j)!∗(−1)j−m ∑ j = m n G [ j ] ∗ ( n − j ) ! ∗ ( − 1 ) j − m

Code

#include <cstdio>
#include <cstdlib>
#include <iostream>
#include <algorithm>
#include <cmath>
#include <cstring>
#define fo(i,a,b) for(int i=a;i<=b;i++)
#define fod(i,a,b) for(int i=a;i>=b;i--)
#define N 1005
#define mo 1000000007
#define LL long long
using namespace std;
LL js[N],g[N],f[N][N][2][2],ny[N];
int n,m;
void add(LL &x,LL y)
{
x=(x+y)%mo;
}
LL C(int n,int m)
{
if(n<m) return 0;
return js[n]*ny[m]%mo*ny[n-m]%mo;
}
int main()
{
cin>>n>>m;
f[0][0][1][0]=1;
js[0]=1;
fo(i,1,n) js[i]=js[i-1]*(LL)i%mo;
fo(i,0,n-1)
{
fo(j,0,i)
{
add(f[i+1][j][0][0],f[i][j][1][0]+f[i][j][0][0]);
add(f[i+1][j][1][0],f[i][j][0][1]+f[i][j][1][1]);

add(f[i+1][j+1][0][0],f[i][j][0][0]);
add(f[i+1][j+1][0][1],f[i][j][0][0]+f[i][j][1][0]);
add(f[i+1][j+1][1][0],f[i][j][0][1]);
add(f[i+1][j+1][1][1],f[i][j][0][1]+f[i][j][1][1]);
}
}
fo(j,0,n)
{
g[j]=(f[n][j][0][0]+f[n][j][1][0])%mo*js[n-j]%mo;
}
ny[0]=ny[1]=1;
fo(i,2,n) ny[i]=(-ny[mo%i]*(LL)(mo/i)+mo)%mo;
fo(i,1,n) ny[i]=ny[i-1]*ny[i]%mo;
LL ans=0;
LL v=1;
fo(j,m,n)
{
ans=(ans+g[j]*C(j,m)%mo*v+mo)%mo;
v=-v;
}
printf("%I64d\n",ans);
}