AGC005D ~K Perm Counting
题意简述:如果一个排列P满足对于所有的i都有\(|P_i-i| \neq k\),则称排列P为合法的。现给出n(排列长度)和k,求有多少种合法的排列,对924844033取模。
clj课件的第一题呢,简单讲下做法。
首先我们看到计数先考虑容斥,答案可以写成\(\sum_{i=0}^{n}(-1)^{i}g[i]*(n-i)!\),其中\(g[i]\)表示有i个不合法的方案,当前瓶颈在于如何求\(g[i]\)。
然后我们构造一个位置集合和权值集合,发现对于任意一个位置\(P\),我们将\(P\)与数值为\(P+k,P-k\)连边,其与无论是位置还是数值,当且仅当它们对\(k\)取余相等时,才会对互相造成影响。然后我们容易发现,这是一个左边为位置,右边为权值的二分图,且有一堆互不干扰的链,问题变成了求任选n条边的方案数。我们直接将这些链处理出来,然后设\(f[i][j][0/1]\)表示当前考虑到第\(i\)个元素,已经选了\(j\)条边,当前边选或不选,简单转移即可。
然后\(g[i]=f[n*2][i][0]+f[n*2][i][1]\),就可以解了。
#include<cstdio>
#include<algorithm>
using namespace std;
const int N=2010,mod=924844033;
int n,k,g[N<<1],f[N<<1][N][2],cnt,fac[N],ans;
int main(){
scanf("%d%d",&n,&k);
for(int i=1;i<=k;i++){
for(int j=i;j<=n;j+=k)g[++cnt]=(j==i);
for(int j=i;j<=n;j+=k)g[++cnt]=(j==i);
}
f[0][0][0]=fac[0]=1;
for(int i=1;i<=n;i++)fac[i]=1LL*i*fac[i-1]%mod;
for(int i=0;i<n*2;i++)
for(int j=0;j<=min(i,n);j++){
f[i+1][j][0]=(f[i][j][0]+f[i][j][1])%mod;
if(!g[i+1])f[i+1][j+1][1]=f[i][j][0];
}
for(int i=0;i<=n;i++){
int tmp=1LL*(f[n<<1][i][0]+f[n<<1][i][1])*fac[n-i]%mod;
ans=(mod+(ans+(i&1?-1:1)*tmp)%mod)%mod;
}
printf("%d\n",ans);
}
agc005_d