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

版权声明:蒟蒻写的文章,能看就行了,同时欢迎大佬们指点错误 https://blog.csdn.net/Algor_pro_king_John/article/details/88976379

Problem

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

给定一个 n m n·m 的矩阵 A A A i , j [ 1 , k ] A_{i,j}\in [1,k] ,定义合法点为这一行这一列中严格最大的点。求矩阵至少有一个合法点的数目。

n , m 2000 , k 10 n,m\le 2000,k\le 10

Solution

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

我们考虑记 f i , j f_{i,j} 表示当前的矩阵最大值小于等于 i i 至少 j j 个合法点的数目。

转移必然是枚举一个值为 i + 1 i+1 的合法点数目了。然后乘上一个组合数进行转移。为了不重不漏,可以把前面 j j 个合法点看成一个整体,那么就剩下 n j n-j 行, m j m-j 列,选出其中 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 k k k 列中只放了 k k 个点,其余点都可以放 [ 1 , i ] [1,i] 中任意一个数,所以要再配上一个形如 i j i^j 的转移系数。

最后对于每个 f i , j f_{i,j} ,还要乘上一个形如 k n k^n 的系数,因为有些格还没放完。

注意转移 k = 0 k=0 时的意义,实际上是做一个前缀和,因为 f i , j f_{i,j} 的含义是最大值小于等于 i i ,并非一定要有 i i 的值。我一开始傻到直接又做一遍前缀和,才发现可以直接 k = 0 k=0 转移。。

Code

#include <bits/stdc++.h>

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

猜你喜欢

转载自blog.csdn.net/Algor_pro_king_John/article/details/88976379
今日推荐