Cheerleaders - UVA 11806 - Virtual Judge
题目大意:有一个n*m的网格,要把其中k个格子涂黑,且满足最上、下一行、最左、右一列分别至少有一格黑,问方案数有多少
2<=m,n<=50;k<=500
思路:因为合法的方案数不好考虑,所以考察不合法的方案数,我们设第一行没有黑格为A,第一列没有黑格为B,最后一行没有黑格为C,最后一列没有黑格为D,根据容斥原理有:非法方案数=-A-B-C-D+AB+AC+AD+BC+BD+CD-ABC-ABD-ACD-BCD+ABCD,这些都可以用组合数算,就是从除了选中行/列以外的格子里选k个,例如A=C[n*m-m][k],AB=C[n*m-n-m+1][k]。
因为题目中的模数不是质数,所以不能用常规打表阶乘逆元的方法算,因为数据范围很小,所以可以用递推转移的方法打表,首先初始化C[i][0]和C[i][i]都是1,对于1到i-1,有c[i][j]=C[i-1]j]+C[i-1][j-1],我们将15个几何都算出来求和即可。
#include<bits/stdc++.h>
//#include<__msvc_all_public_headers.hpp>
using namespace std;
typedef long long ll;
const int N = 1e6 + 5;
const ll MOD = 1e6 + 7;
ll n;
ll inv[N], fac[N];
ll C[405][405];
void calC()
{//打表预处理组合数
C[0][0] = 1;
for (int i = 1; i <= 400; i++)
{
C[i][0] = C[i][i] = 1;//边界都是1
for (int j = 1; j < i; j++)
{//中间用类似杨辉三角的形式转移
C[i][j] = C[i - 1][j] + C[i - 1][j - 1] % MOD;
}
}
}
void solve(int t)
{
ll m, k;
cin >> n >> m >> k;
ll ans = C[n * m][k];//总方案数
ans = (ans - C[n * m - n][k] + MOD) % MOD;//A
ans = (ans - C[n * m - n][k] + MOD) % MOD;//B
ans = (ans - C[n * m - m][k] + MOD) % MOD;//C
ans = (ans - C[n * m - m][k] + MOD) % MOD;//D
ans = (ans + C[n * m - n - m + 1][k]) % MOD;//AB
ans = (ans + C[n * m - n - m + 1][k]) % MOD;//AD
ans = (ans + C[n * m - n - n][k]) % MOD;//AC
ans = (ans + C[n * m - m - m][k]) % MOD;//BD
ans = (ans + C[n * m - m - n + 1][k]) % MOD;//BC
ans = (ans + C[n * m - n - m + 1][k]) % MOD;//CD
ans = (ans - C[n * m - 2 * n - m + 2][k] + MOD) % MOD;//ABC
ans = (ans - C[n * m - 2 * n - m + 2][k] + MOD) % MOD;//ACD
ans = (ans - C[n * m - 2 * m - n + 2][k] + MOD) % MOD;//BCD
ans = (ans - C[n * m - 2 * m - n + 2][k] + MOD) % MOD;//ABD
ans = (ans + C[n * m - 2 * m - 2 * n + 4][k]) % MOD;//ABCD
cout << "Case " << t << ": " << ans % MOD << endl;
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(0);
int t;
cin >> t;
calC();
int cnt = 0;
while (t--)
{
cnt++;
solve(cnt);
}
return 0;
}