https://jzoj.net/senior/#main/show/4779
给定一个 n ⋅ m n·m n⋅m的矩阵 A A A, A i , j ∈ [ 1 , k ] A_{i,j}\in [1,k] Ai,j∈[1,k],定义合法点为这一行这一列中严格最大的点。求矩阵至少有一个合法点的数目。
n , m ≤ 2000 , k ≤ 10 n,m\le 2000,k\le 10 n,m≤2000,k≤10
首先肯定是考虑容斥了。方案数 = 至少有一个合法点数目 - 至少有两个 +至少有三个 ……
我们考虑记 f i , j f_{i,j} fi,j表示当前的矩阵最大值小于等于 i i i,至少有 j j j个合法点的数目。
转移必然是枚举一个值为 i + 1 i+1 i+1的合法点数目了。然后乘上一个组合数进行转移。为了不重不漏,可以把前面 j j j个合法点看成一个整体,那么就剩下 n − j n-j n−j行, m − j m-j m−j列,选出其中 k k k种组合放合法点,由于这 k k k行列顺序随意,所以方案数就是 k ! ( n − j k ) ( m − j k ) ∗ f i , j → f i + 1 , j + k k!\binom{n-j}{k}\binom{m-j}{k}*f_{i,j}\rightarrow f_{i+1,j+k} k!(kn−j)(km−j)∗fi,j→fi+1,j+k当然,因为这 k k k行 k k k列中只放了 k k k个点,其余点都可以放 [ 1 , i ] [1,i] [1,i]中任意一个数,所以要再配上一个形如 i j i^j ij的转移系数。
最后对于每个 f i , j f_{i,j} fi,j,还要乘上一个形如 k n k^n kn的系数,因为有些格还没放完。
注意转移 k = 0 k=0 k=0时的意义,实际上是做一个前缀和,因为 f i , j f_{i,j} fi,j的含义是最大值小于等于 i i i,并非一定要有 i i i的值。我一开始傻到直接又做一遍前缀和,才发现可以直接 k = 0 k=0 k=0转移。。
#include
#define F(i, a, b) for (LL i = a; i <= b; i ++)
typedef long long LL;
const LL N = 2e3 + 10, K = 11;
using namespace std;
LL n, m, k, l, ans;
LL f[N][K], C[N][N], jc[N], KSM[K][2 * N * N];
int main() {
scanf("%d%d%d%d", &n, &m, &k, &l);
jc[0] = 1;
F(i, 0, max(n, m) + 1)
C[i][0] = 1;
F(i, 1, max(n, m) + 1)
F(j, 1, max(n, m) + 1)
C[i][j] = (C[i - 1][j - 1] + C[i - 1][j]) % l;
F(i, 1, max(n, m))
jc[i] = 1ll * jc[i - 1] * i % l;
F(j, 1, k) {
KSM[j][0] = 1;
F(i, 1, 2 * n * m)
KSM[j][i] = KSM[j][i - 1] * j % l;
}
f[0][1] = 1; int w = min(n, m);
F(i, 0, w)
F(j, 1, k - 1)
if (f[i][j])
F(t, 0, w - i)
if (n - i >= t && m - i >= t)
f[i + t][j + 1] = (f[i + t][j + 1] + 1ll * f[i][j] * C[n - i][t] % l * C[m - i][t] % l * jc[t] % l * KSM[j][(n + m - 2 * i - t - 1) * t] % l) % l;
F(i, 1, w)
ans = (ans + ((i & 1) ? (1) : (- 1)) * f[i][k] * KSM[k][(n - i) * (m - i)] % l) % l;
printf("%d\n", (ans + l) % l);
}