原题链接:P6076 [JSOI2015]染色问题
题意
萌萌家有一个棋盘,这个棋盘是一个
n
×
m
n \times m
n×m 的矩形,分成
n
n
n 行
m
m
m 列共
n
×
m
n \times m
n×m 个小方格。
现在萌萌和南南有
C
C
C 种不同颜色的颜料,他们希望把棋盘用这些颜料染色,并满足以下规定:
- 棋盘的每一个小方格既可以染色(染成 C C C 种颜色中的一种),也可以不染色。
- 棋盘的每一行至少有一个小方格被染色。
- 棋盘的每一列至少有一个小方格被染色。
- 每种颜色都在棋盘上出现至少一次。
( 1 ≤ n , m , c ≤ 400 1\le n,m,c\le 400 1≤n,m,c≤400)
只考虑黑白
首先考虑一个简化的问题,即只有黑白两种颜色:给一个 n × m n\times m n×m 的矩形,给其中一些各自染成黑色,要求每一行每一列都至少有一个小方格是黑色,求方案数量。
设 f ( x ) f(x) f(x) 为:钦定有 x x x 列不染色,剩余的 m − x m-x m−x 列 $ n$ 行部分保证每一行都至少有一个格子染色的方案数量。
那么通过容斥可以得出这个子问题的答案
a
n
s
=
f
(
0
)
−
f
(
1
)
+
f
(
2
)
…
ans = f(0)-f(1)+f(2)\dots
ans=f(0)−f(1)+f(2)…
而
f
(
x
)
f(x)
f(x) 的表达式也比较容易得出
f
(
x
)
=
(
m
x
)
(
2
m
−
x
−
1
)
n
f(x) = {m\choose x}(2^{m-x}-1)^n
f(x)=(xm)(2m−x−1)n
简要介绍这个式子的含义。 ( m x ) {m\choose x} (xm) 是从 m m m 列中钦定 x x x 列不染色。 2 m − x − 1 2^{m-x}-1 2m−x−1 是每一行的方案数(-1是因为不能为空)。套上 n n n 次幂就是 n n n 行的方案数量。
考虑 c c c 种颜色
设 g ( t ) g(t) g(t) 表示:钦定不使用 t t t 种颜色,完成上述问题的方案数。
那么答案通过容斥可以得出
a
n
s
=
g
(
0
)
−
g
(
1
)
−
g
(
2
)
…
ans = g(0)-g(1)-g(2)\dots
ans=g(0)−g(1)−g(2)…
而 g ( t ) g(t) g(t) 的求法其实就是上一个问题。
仍然设钦定
x
x
x 列不染色,剩余的
m
−
x
m-x
m−x 列 $ n$ 行部分保证每一行都至少有一个格子染色的方案数量为
f
(
t
)
f(t)
f(t),容易得出
f
t
(
x
)
=
(
m
x
)
(
(
c
−
t
)
m
−
x
−
1
)
n
f_t(x) = {m\choose x}((c-t)^{m-x}-1)^n
ft(x)=(xm)((c−t)m−x−1)n
这个式子的意义同上,只不过把 2 2 2 换成了 c − t c-t c−t,表示可以使用的颜色数量。
所以有
g
(
t
)
=
f
t
(
0
)
−
f
t
(
1
)
+
f
t
(
2
)
…
g(t) = f_t(0) - f_t(1) +f_t(2)\dots
g(t)=ft(0)−ft(1)+ft(2)…
综上,本题需要两层容斥得出答案。
#include <bits/stdc++.h>
using namespace std;
#define int long long
#define PII pair<int, int>
int Testnum = 1;
/********************** Core code begins **********************/
const int N = 1003, MOD = 1e9 + 7;
int n, m, c;
int C[N][N];
void init() {
for (int i = 0; i < N; i++) {
C[i][0] = 1;
for (int j = 1; j <= i; j++) {
C[i][j] = (C[i - 1][j] + C[i - 1][j - 1]) % MOD;
}
}
}
int qpow(int x, int k) {
int res = 1;
while (k) {
if (k & 1) {
res = res * x % MOD;
}
k /= 2;
x = x * x % MOD;
}
return res;
}
// 至少有 x 种颜色没有使用,即使用了不超过 k = c - x 种颜色
int work(int x) {
int k = c - x;
int res = 0, sig = 1;
// 枚举不超过 i 列有涂色,再做一遍容斥。
for (int i = m; i >= 1; i--) {
res = (res + C[m][i] * qpow((qpow(k + 1, i) - 1), n) % MOD * sig % MOD) % MOD;
sig = MOD - sig;
}
res = res * C[c][x] % MOD;
return res;
}
void SolveTest() {
cin >> n >> m >> c;
init();
int res = 0, sig = 1;
for (int i = 0; i <= c; i++) {
res = (res + work(i) * sig % MOD) % MOD;
// printf("work(%lld) == %lld\n", i, work(i));
sig = MOD - sig;
}
cout << res;
}
/********************** Core code ends ***********************/
signed main() {
#ifdef LOCAL
freopen("in.txt", "r", stdin);
#endif
// cin >> Testnum;
for (int i = 1; i <= Testnum; i++) {
SolveTest();
}
return 0;
}