题意
给定一个n * m矩阵,和 k 个石子,将 k 个石子全都放到这个矩阵中,使得第一行、第一列、最后一行、最后一列都必须要有石子,请问有多少种放法?
分析
问题的正面不容易解,我们就从问题的反面开始研究,我们将k个石子没有限制的放到矩阵中的方法定义为全集S,我们所想要求解的集合T也一定在这个全集中,我们分析后发现,如果我们设第一行没有石子的情况为集合A,第一列没有石子为集合B,最后一行没有石子为集合C,最后一列没有石子为集合D,那么我们的
T=S−|A∪B∪C∪D|
根据容斥定理有
|A∪B∪C∪D|=|A|+|B|+|C|+|D|−|A∩B|−|A∩C|−|A∩D|−|B∩C|−|B∩D|−|B∩D|...
计算集合A、B、C、D的值都不是难事,所以我们可以很方便的求解问题啦。
另外在编码中我们常用状态压缩和为运算来完成集合的交并运算。我们用值s的二进制位的每一位来表示一个集合是否参与交,之后统计参与交的集合个数就可以知道这个状态是应该剪去还是加上了。
代码
C++
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
using namespace std;
const int MOD = 1000007;
const int maxk = 500;
int n, m, k, c[maxk + 10][maxk + 10];
void init(){
memset(c, 0, sizeof(c));
c[0][0] = 1;
for(int i = 0; i <= maxk; i++){
c[i][0] = c[i][i] = 1;
for(int j = 1; j < i; j++){
c[i][j] = (c[i -1][j] + c[i - 1][j - 1]) % MOD;
}
}
}
int main(int argc, const char * argv[]) {
//freopen("/Users/zhangjiatao/Desktop/input.txt", "r", stdin);
int T;
scanf("%d", &T);
init();
for(int t = 1; t <= T; t++){
scanf("%d%d%d", &n, &m, &k);
int sum = 0;
for(int s = 0; s < 16; s ++){
int r = n, l = m, b = 0;
if(s & (1 << 3)) {r--; b++;}
if(s & (1 << 2)) {r--; b++;}
if(s & (1 << 1)) {l--; b++;}
if(s & (1 << 0)) {l--; b++;}
if(b & 1){
sum = (sum + MOD - c[r * l][k]) % MOD;
}
else{
sum = (sum + c[r * l][k]) % MOD;
}
//cout <<b<<","<<s<<","<< sum << endl;
}
printf("Case %d: %d\n", t, sum);
}
return 0;
}