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;	
}

posted @ 2022-09-29 09:39  天穹の流星  阅读(108)  评论(0)    收藏  举报