JZOJ4779. 【GDOI2017模拟9.14】鞍点

题目大意

有一个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
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值