4779. 【GDOI2017模拟9.14】鞍点(组合计数 +容斥)

Problem

https://jzoj.net/senior/#main/show/4779

给定一个 n ⋅ m n·m nm的矩阵 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,m2000,k10

Solution

首先肯定是考虑容斥了。方案数 = 至少有一个合法点数目 - 至少有两个 +至少有三个 ……

我们考虑记 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 nj行, m − j m-j mj列,选出其中 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!(knj)(kmj)fi,jfi+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转移。。

Code

#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);
}

你可能感兴趣的:(容斥原理,计数)