「JOISC 2018 Day 1」帐篷

题意:(n)(m)列的矩阵((n,m<=3000)),在格子里搭一些帐篷.每座帐篷必须占据刚好一个格子.没有两座帐篷会占据同一个格子.每座帐篷在东、南、西、北四个方向之一有一个出入口.帐篷的出入口朝向必须满足以下条件:同一行或同一列的两个帐篷的出入口要相对(例如同一行的两个帐篷,左(西)边的帐篷出入口对着右(东)边,右边的对着左边).求在矩阵中至少放一个帐篷的方案数.

分析:根据这个放帐篷的条件,我们可以发现,我们放的帐篷只可能是如下三种情况之一:

第一种:一个帐篷占据了一行和一列,那么这个帐篷的出入口的朝向是四个方向任意一个;

第二种:两个帐篷占据了一行和两列;

第三种:两个帐篷占据了两行和一列;

(f[i][j])表示当前放第i行和第j列的方案数.

对应第一种情况,(f[i][j]+=f[i-1][j-1]*4*C_j^1)

对应第二种情况,(f[i][j]+=f[i-1][j-2]*C_j^2)

对应第三种情况,(f[i][j]+=f[i-2][j-1]*C_{i-1}^1*C_j^1)

解释一下状态转移方程中可能产生疑问的地方.

为什么情况二不要乘上选哪一行的情况?因为我们的外层循环默认了一个填帐篷的顺序,即从第1行到第n行,然后情况二我们默认就是填的就是第i行.

为什么情况三要乘上选哪一行?因为我们一次要填两行,而这两行不一定是相邻的两行.所以我们只确定了要填第i行,然后要在前i-1行中再选一行来填.

这个只是f数组的状态转移,我们考虑如何统计答案?注意到题目只是要保证至少填一个帐篷即可,那么必然有些方案会有一些空行和空列没有填,所以(ans=sum_{i=1}^nsum_{j=1}^mf[i][j]*C_n^i*C_m^j)

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
#include<queue>
#include<map>
#include<set>
#define ll long long
using namespace std;
inline int read(){
    int x=0,o=1;char ch=getchar();
    while(ch!='-'&&(ch<'0'||ch>'9'))ch=getchar();
    if(ch=='-')o=-1,ch=getchar();
    while(ch>='0'&&ch<='9')x=x*10+ch-'0',ch=getchar();
    return x*o;
}
const int mod=1e9+7;
const int N=3005;
ll ans,jc[N],inv[N],f[N][N];
inline ll ksm(ll a,int b){
	ll cnt=1;
	while(b){
		if(b&1)cnt=(1ll*cnt*a)%mod;
		a=(1ll*a*a)%mod;
		b>>=1;
	}
	return cnt;
}
inline ll C(int n,int m){return (1ll*jc[n]*inv[n-m]%mod*inv[m])%mod;}
int main(){
	jc[0]=1;for(int i=1;i<=3000;++i)jc[i]=(1ll*jc[i-1]*i)%mod;
	inv[3000]=ksm(jc[3000],mod-2);
	for(int i=3000;i>=1;--i)inv[i-1]=(1ll*inv[i]*i)%mod;
	int n=read(),m=read();f[0][0]=1;
	for(int i=1;i<=n;++i)
		for(int j=1;j<=m;++j){
			f[i][j]=(f[i][j]+((1ll*f[i-1][j-1]*j)%mod*4)%mod)%mod;
			if(j>=2)f[i][j]=(f[i][j]+(1ll*f[i-1][j-2]*C(j,2))%mod)%mod;
			if(i>=2)f[i][j]=(f[i][j]+((1ll*f[i-2][j-1]*(i-1))%mod*j)%mod)%mod;
		}
	for(int i=1;i<=n;++i)
		for(int j=1;j<=m;++j)
			ans=(ans+(1ll*f[i][j]*C(n,i)%mod*C(m,j))%mod)%mod;
	printf("%lld
",ans);
    return 0;
}

原文地址:https://www.cnblogs.com/PPXppx/p/11372680.html