题目大意
有一个N*M的矩阵,每个位置可以填整数[1,K]。求至少有一个鞍点的矩阵有多少个。
鞍点(i,j)定义:在行i和列j所有元素中,(i,j)的值是严格最大的,即没有重复。
n,m≤2000,K≤10.
分析
求方案数,我们可以往容斥原理方面想一想。设个f[i][j]表示鞍点值≤i,鞍点数至少有j个的方案数。我们每确定一个鞍点,就可以把鞍点所在行列的填数方案统计出来。
鞍点选不同位置会分割出很多个区间,怎么破?我们可以把矩阵重新排列,鞍点位于从左上方开始的对角线上。之前确定过的行列,是不会对新鞍点有影响的,因为我们i是小到大枚举的嘛。
考虑转移
对于状态(i,j),我们新搞出x个鞍点,值为j+1那么这些鞍点所在行列的方案:
Cxn−i∗Cxm−i∗x!
。于此同时,他们所处的行列的其他格子,共
(m−i)∗x+(n−i)∗x−x2−x
个就只能取1~j的值了。
顺利实现转移后,我们要统计答案了。
很显然最后的答案应该是f[K][j]里面的,注意到可能有些行列还是空白没有填的,我们要乘上
j(m−i)∗(n−i)
。
答案要容斥:
∑min(n,mj=1f[K][j]∗(−1)j+1
代码
#include<cstdio>
#include<algorithm>
using namespace std;
#define fo(i,j,k) for(i=j;i<=k;i++)
typedef long long ll;
const int N=2005;
ll n,m,K,l,mn,tt,k,mo,ans;
int i,j,x;
ll c[N][N],jc[N],kx[11][N*N],f[N][N];
int main()
{
scanf("%lld%lld%lld%lld",&n,&m,&K,&mo);
c[0][0]=1;
fo(i,1,2000)
{
c[i][0]=1;
fo(j,1,i)
c[i][j]=(c[i-1][j-1]+c[i-1][j])%mo;
}
f[0][0]=1;
mn=min(m,n);
jc[0]=1;
fo(i,1,mn) jc[i]=jc[i-1]*(ll)i%mo;
fo(j,0,K)
{
kx[j][0]=1;
fo(i,1,n*m)
kx[j][i]=kx[j][i-1]*(ll)j%mo;
}
fo(i,0,K-1)
fo(j,0,mn)
if (f[i][j])
{
fo(x,0,mn-j)
{
(f[i+1][j+x]+=f[i][j]*c[n-j][x]%mo*c[m-j][x]%mo*jc[x]%mo
*kx[i][(m-j)*x+(n-j)*x-x*x-x]%mo)%=mo;
}
}
j=-1;
fo(i,1,mn)
{
j=-j;
ans=(ans+f[K][i]*kx[K][(m-i)*(n-i)]%mo*j)%mo;
ans=(ans+mo)%mo;
}
printf("%lld\n",ans);
}