SMOJ 排列
题面:


-
\(分析问题\)
首先,我们可以将这个序列对应到二维矩阵上。
其中横坐标表示对应元素的下标,纵坐标表示 \(a[i]\)。
原序列的第 \(i\) 位为 \(a[i]\) 可以在二维矩阵上用一个点 \((i,a[i])\) 表示。
问题变成了在 \(n*n\) 的二维矩阵上放 \(n\) 个互不攻击的车(即每行每列有且仅有一个车)。
第 \(i\) 行的车有贡献当且仅当放在 \(i-1,i,i+1\) 列上
我们要使有贡献的恰好有 \(m\) 个
-
\(解决问题\)
直接求恰好 \(m\) 个显然不好直接求,我们考虑容斥(二项式反演),先求至少 \(m\) 个有贡献。
设 \(f(i)\) 表示恰好 \(i\) 个有贡献的方案数 ,\(g(i)\)表示至少 \(i\) 个有贡献的方案数。
则有 \(f(i)=\sum _{j=i}^{n} {(-1)^{j-i}\ {j \choose i} g(i)}\)
现在问题变成了怎么求 \(g(i)\)
我们考虑先算有贡献的棋子,剩下的棋子随便放。
又由于对于一行的车,有贡献的只有三个位置,因此我们考虑状态压缩。
设 \(dp(i,j,s)\) 表示现在考虑完第 \(i\) 行,已经放了 \(j\) 个有贡献的棋子,且目前状态为 \(s\) 的方案数。
状态 \(s\) 为两位二进制数,其中第一位 \(0/1\) 表示第 \(i\) 列有没有放,第二位 \(0/1\) 表示第 \(i+1\) 列有没有放。
我们考虑转移,
第 \(i\) 行的棋子有4种放法,放第 \(i-1\) , \(i\) , \(i+1\) 列 或者 不放。
放第 \(i-1\) 列,则若 \(s\) 满足 \(s \& 2==0\),则有 \(dp(i,j,(s\&1)<<1)+=dp(i-1,j-1,s)\)
放第 \(i-1\) 列,则若 \(s\) 满足 \(s \& 1==0\),则有 \(dp(i,j,2)+=dp(i-1,j-1,s)\)
放第 \(i+1\) 列,则有\(dp(i,j,(s\&1)<<1 |1)+=dp(i-1,j-1,s)\)
不放,则有\(dp(i,j,(s\&1)<<1)+=dp(i-1,j,s)\)
特别地,初始化为 \(dp(0,0,2)=1\),因为第 \(0\) 列不能放,同理最终 \(g(i)= dp(n,i,0)+dp(n,i,2)\),因为第 \(n+1\) 列也不能放。
算出 \(dp(i,j,s)\) 后但还有剩下 \(i-j\) 个车没有放,因为是至少,这些随便放,所以最后乘上 $ (i-j)!$
预处理出 \(dp(i,j,s)\) ,时间复杂度 \(O(n^2)\)
对于每次询问,直接算出 \(g(i)\) 进行二项式反演即可,复杂度 \(O(n)\) ,总复杂度 \(O(qn)\)
代码
#pragma GCC optimize(3,"Ofast","inline")
#include<bits/stdc++.h>
using namespace std;
long long f[2005][2005][5];
long long p;
__int128 fact[2005];
__int128 C[2005][2005];
int T;
int main(){
freopen("perm.in","r",stdin);
freopen("perm.out","w",stdout);
scanf("%d%lld",&T,&p);
f[0][0][2]=1;
for(int i=1;i<=2000;i++){
for(int j=0;j<=i;j++){
for(int s=0;s<4;s++){
if(!(s&2))f[i][j+1][s*2]+=f[i-1][j][s],f[i][j+1][s*2+1]%=p;
if(!(s&1))f[i][j+1][2]+=f[i-1][j][s],f[i][j+1][2]%=p;
f[i][j+1][(s&1)*2+1]+=f[i-1][j][s],f[i][j+1][(s&1)*2+1]%=p;
f[i][j][(s&1)*2]+=f[i-1][j][s],f[i][j][(s&1)*2]%=p;
}
}
}
C[0][0]=1;
for(int i=1;i<=2000;i++){
C[i][0]=1;
for(int j=1;j<=i;j++)
C[i][j]=(C[i-1][j-1]+C[i-1][j])%p;
}
fact[0]=1;
for(int i=1;i<=2000;i++)fact[i]=fact[i-1]*i%p;
while(T--){
int n,m;
scanf("%d%d",&n,&m);
long long ans=0;
for(int i=m;i<=n;i++){
long long now=(f[n][i][0]+f[n][i][2])%p;
if((i-m)&1)ans=(ans-C[i][m]*fact[n-i]%p*now%p+p)%p;
else ans=(ans+C[i][m]*fact[n-i]%p*now%p)%p;
}
printf("%lld\n",ans);
}
return 0;
}

浙公网安备 33010602011771号