[JZOJ6021]【GDOI2019模拟2019.2.15】车【容斥原理】【计数】

Description

将 n 个车摆在 n × n 的棋盘上,每个格子最多摆放一个,并且每行每列和两条最长的对角线上至少有一个车,并且有 m 个格子不能摆放。问方案数。T组数据
在这里插入图片描述

Solution

每行每列至少有一个,且总个数等于行数,那就是每行每列恰好有一个。

我们发现m很小,显然这就是让你2m2^m2m容斥的
即强制选出一些不能选的位必须选,乘上(-1)^强制选的个数

这样相当于某些位置必须选,这些行列的其他格子不再可用。

考虑剩下的怎么做。

显然答案=没有任何限制方案数-左上右下对角线为空方案数-右上左下对角线为空方案数+两条对角线均为空的方案数。

一条对角线为空是容易计算的,同样采用容斥,我们先求出这条对角线上还有多少个格子可以用,枚举在这条对角线上强制选了多少个格子即可,剩下的行列直接阶乘随便选。

考虑两条对角线均为空。
也是容斥,假设两条对角线上分别选了x,y个格子,容斥系数可以推得就是(-1)^(x+y)
当n为奇数时,选取中心会让x,y都+1,所以此时的x+y要-1
也就是说容斥系数就是(-1)^两条对角线上总共选取的格子数,这样具体的x,y是无用的。

题解给出了一种非常巧妙的DP
我们从外向内一环一环的做,即对于一个i,考虑(i,i),(i,n−1−i),(n−1−i,i),(n−1−i,n−1−i)(i,i),(i,n-1-i),(n-1-i,i),(n-1-i,n-1-i)(i,i),(i,n1i),(n1i,i),(n1i,n1i)这四个格子,将它们放到一起DP,显然环与环之间两两独立,每个环可以选0,1,2个格子。

那么就可以用背包做了,F[i][j]F[i][j]F[i][j]表示做到第i个环,选了j个格子。
中心的一个相当于只有一个格子的特殊环。

最后统计答案同上面类似的。

总的时间复杂度O(T∗n2∗2m)O(T*n^2*2^m)O(Tn22m)

Code

#include <bits/stdc++.h>
#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 115
#define mo 10007
using namespace std;
typedef long long LL;
int n,m,t,a[N],a1[12][2];
LL ans,js[N],ny[N],f[N][N];
bool bz[N],bf[N],bc[N];
LL ksm(LL k,LL n)
{
	LL s=1;
	for(;n;n>>=1,k=k*k%mo) if(n&1) s=s*k%mo;
	return s;
}
LL C(int n,int m)
{
	if(n<m) return 0;
	return js[n]*ny[m]%mo*ny[n-m]%mo;
}
void dfs(int k,bool p1,bool p2,LL v,int c)
{
	if(k>m)
	{
		LL s1=js[c],s2=0,s3=0,s4=0;
		if(!p1)
		{
			int cnt=0;LL vp=1;
			fo(i,0,n-1) if(!bf[i]&&!bc[i]) cnt++;
			fo(i,0,cnt) 
			{
				s2=(s2+vp*js[c-i]*C(cnt,i)%mo+mo)%mo;
				vp=-vp;
			}
		}
		if(!p2)
		{
			int cnt=0;LL vp=1;
			fo(i,0,n-1) if(!bf[i]&&!bc[n-1-i]) cnt++;
			fo(i,0,cnt) 
			{
				s3=(s3+vp*js[c-i]*C(cnt,i)%mo+mo)%mo;
				vp=-vp;
			}	
		}
		if(!p1&&!p2)
		{
			memset(f,0,sizeof(f));
			f[0][0]=1;
			fo(i,0,(n+1)/2-1)
			{
				fo(j,0,2*(i+1))
				{
					f[i+1][j]=f[i][j];
					if(n-1-i!=i)
					{	
						if(j>0) 
						{
							LL c1=(!bf[i]&&!bc[i])+(!bf[i]&&!bc[n-1-i])+(!bf[n-1-i]&&!bc[i])+(!bf[n-1-i]&&!bc[n-1-i]);
							f[i+1][j]=(f[i+1][j]+f[i][j-1]*c1)%mo;
						}
						if(j>1)
						{
							LL c1=2*(!bf[i]&&!bc[i]&&!bf[n-1-i]&&!bc[n-1-i]);
							f[i+1][j]=(f[i+1][j]+f[i][j-2]*c1)%mo;
						}
					}
					else if(!bf[i]&&!bc[i]&&j>0) f[i+1][j]=(f[i+1][j]+f[i][j-1])%mo;
				}
			}
			LL vp=1;
			fo(i,0,n) s4=(s4+vp*js[c-i]*f[(n+1)/2][i]%mo+mo)%mo,vp=-vp;
		}
		ans=(ans+v*(s1-s2-s3+s4)%mo+mo)%mo;
		return;
	}
	dfs(k+1,p1,p2,v,c);
	int x=a1[k][0],y=a1[k][1];
	if(!bf[x]&&!bc[y])
	{
		bf[x]=1,bc[y]=1;
		dfs(k+1,p1||(x==y),p2||(x==n-1-y),-v,c-1);
		bf[x]=0,bc[y]=0;
	}
}		
int main()
{
	cin>>t;
	js[0]=1;
	fo(i,1,100) js[i]=js[i-1]*(LL)i%mo;
	ny[100]=ksm(js[100],mo-2);
	fod(i,99,0) ny[i]=ny[i+1]*(LL)(i+1)%mo;
	while(t--)
	{
		scanf("%d%d",&n,&m);
		memset(bf,0,sizeof(bf));
		memset(bc,0,sizeof(bc));
		ans=0;
		fo(i,1,m) scanf("%d%d",&a1[i][0],&a1[i][1]);
		dfs(1,0,0,1,n);
		printf("%lld\n",ans);
	}
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值