一、题目
二、解法
考虑权值的组合意义,考虑一个矩阵 \(B\),它需要满足 \(b(i,j)\leq \min(a(i,k),a(k,j))\),进一步转化就是 \(B\) 第 \(i\) 行的最大值 \(\leq A\) 第 \(i\) 行的最小值,并且 \(B\) 第 \(j\) 列的最大值 \(\leq A\) 第 \(j\) 列的最小值,权值和就是合法的 \((A,B)\) 对数。
我们按照 \(k\) 从小到大地填到 \(A,B\) 里面,考虑到限制的特殊性,我们记录下 \(B\) 有多少行的最大值已经被确定了,\(A\) 有多少列的最小值已经被确定了,那么转移就考虑 \(B\) 中新增一些行的最大值 \(=k\) 和 \(A\) 中新增一些列的最小值 \(=k\):
- 先新增 \(B\) 的行,考虑这个行上的每一个位置。如果对应的列上 \(A\) 的最小值已经确定(并且一定 \(<k\)),那么为了适配限制,我们往这个位置的 \(A\) 上填入一个 \(\geq k\) 的数;如果对应的列上 \(A\) 的最小值还未确定,那么可以在 \(B\) 中填入一个 \(\leq k\) 的数,并且要求这些数中至少有一个 \(=k\)
- 再新增 \(A\) 的列,考虑这个列上的每一个位置。如果对应的行上 \(B\) 的最大值已经确定(并且一定 \(\leq k\)),那么为了适配限制,我们往这个位置的 \(A\) 上填入一个 \(\geq k\) 的数,并且要求这些数中至少有一个 \(=k\);如果对应的列上 \(B\) 的最大值还未确定,那么可以在 \(B\) 中填一个 \(\leq k\) 的数。
发现转移系数就是组合数(选出行列)乘上幂次(选数)的形式,所以可以 \(O(n^3)\) 预处理出转移系数,然后 \(O(n^4)\) 暴力 \(dp\)
#include <cstdio>
#include <iostream>
using namespace std;
#define int long long
const int M = 105;
int read()
{
int x=0,f=1;char c;
while((c=getchar())<'0' || c>'9') {if(c=='-') f=-1;}
while(c>='0' && c<='9') {x=(x<<3)+(x<<1)+(c^48);c=getchar();}
return x*f;
}
int n,m,k,p,C[M][M],g[M][M][M][2],f[M][M][M][2];
int sub(int a,int b) {return (a-b+p)%p;}
void add(int &x,int y) {x=(x+y)%p;}
int qkpow(int a,int b)
{
int r=1;
while(b>0)
{
if(b&1) r=r*a%p;
a=a*a%p;
b>>=1;
}
return r;
}
signed main()
{
n=read();m=read();k=read();p=read();
for(int i=0;i<=max(n,m);i++)
{
C[i][0]=1;
for(int j=1;j<=i;j++)
C[i][j]=(C[i-1][j-1]+C[i-1][j])%p;
}
for(int c=1;c<=k;c++) for(int i=0;i<=m;i++)
{
int t=qkpow(k-c+1,i)*sub(qkpow(c,m-i)
,qkpow(c-1,m-i))%p;
g[c][i][0][0]=1;
for(int j=1;j<=n;j++)
g[c][i][j][0]=g[c][i][j-1][0]*t%p;
}
for(int c=1;c<=k;c++) for(int i=0;i<=n;i++)
{
int t=qkpow(c,n-i)*sub(qkpow(k-c+1,i)
,qkpow(k-c,i))%p;
g[c][i][0][1]=1;
for(int j=1;j<=m;j++)
g[c][i][j][1]=g[c][i][j-1][1]*t%p;
}
f[1][0][0][0]=1;
for(int c=1;c<=k;c++) for(int i=0;i<=n;i++)
for(int j=0;j<=m;j++)
{
for(int l=0;l<=n-i;l++)
add(f[c][i+l][j][1],f[c][i][j][0]*
C[n-i][l]%p*g[c][j][l][0]);
for(int l=0;l<=m-j;l++)
add(f[c+1][i][j+l][0],f[c][i][j][1]*
C[m-j][l]%p*g[c][i][l][1]);
}
printf("%lld\n",f[k+1][n][m][0]);
}