JZOJ4779. 【GDOI2017模拟9.14】鞍点

来源:互联网 发布:淘宝聚划算开团抢技巧 编辑:程序博客网 时间:2024/06/07 15:02

题目大意

有一个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那么这些鞍点所在行列的方案:CxniCxmix!。于此同时,他们所处的行列的其他格子,共(mi)x+(ni)xx2x个就只能取1~j的值了。
顺利实现转移后,我们要统计答案了。
很显然最后的答案应该是f[K][j]里面的,注意到可能有些行列还是空白没有填的,我们要乘上j(mi)(ni)
答案要容斥: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);}
0 0
原创粉丝点击