题目链接:https://onlinejudge.org/index.php?option=com_onlinejudge&Itemid=8&page=show_problem&problem=4035
特别恶心的分类讨论,讨论四个角的放置方法即可
具体讨论见代码
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 1000010;
const int M = 1000000007;
int T, n, m, k;
int fac[maxn], ifac[maxn];
int f[105], g[10];
int qsm(int i, int po){
int res = 1;
while(po){
if(po & 1) res = 1ll * res * i % M;
i = 1ll * i * i % M;
po >>= 1;
}
return res;
}
int C(int N, int K){
if(K < 0) return 0;
if(N < K) return 0;
return 1ll * fac[N] * ifac[K] % M * ifac[N-K] % M;
}
ll read(){ ll s = 0, f = 1; char ch = getchar(); while(ch < '0' || ch > '9'){ if(ch == '-') f = -1; ch = getchar(); } while(ch >= '0' && ch <= '9'){ s = s * 10 + ch - '0'; ch = getchar(); } return s * f; }
int main(){
fac[0] = 1;
for(int i = 1 ; i <= 1000000 ; ++i) fac[i] = 1ll * fac[i-1] * i % M;
ifac[1000000] = qsm(fac[1000000], M - 2);
for(int i = 1000000 - 1 ; i >= 0 ; --i) ifac[i] = 1ll * ifac[i+1] * (i+1) % M;
scanf("%d", &T);
int kase = 0;
while(T--){
int ans = 0;
scanf("%d%d%d", &n, &m, &k);
printf("Case %d: ", ++kase);
if(n == 1 || m == 1){
printf("%d
", C(n*m, k));
continue;
}
// case7 (填四个角)
int ans7 = 0;
ans7 = C(n*m-4, k-4);
ans = (ans + ans7) % M;
// case6 (只填三个角) 答案乘 4
int ans6 = 0;
ans6 = C(n*m-4, k-3);
ans = (ans + 2ll * ans6 % M * 2 % M) % M;
// case1 (只填两个对角) 答案乘 2
int ans1 = 0;
ans1 = C(n*m-4, k-2);
// printf("%d
", ans1);
ans = (ans + 2ll * ans1 % M) % M;
// case2 (只填第一行的两个角) 答案乘 2
int ans2 = 0;
// case2.1 最后一行放
ans2 = (ans2 + ((C(n*m-4, k-2) - C((n-1)*m-2, k-2)) % M + M) % M) % M;
// case2.2 最后一行不放 (容斥)
memset(f, 0, sizeof(f));
for(int i = 0 ; i <= m-2 ; ++i){
if(k <= (n-1)*(m-i)) { // 能放得下
f[i] = 1ll * C(m-2, i) * C((n-1)*(m-i)-2, k-2) % M;
}
if(i & 1){
ans2 = ((ans2 - f[i]) % M + M) % M;
} else{
ans2 = (ans2 + f[i]) % M;
}
}
ans = (ans + 2ll * ans2 % M) % M;
// printf("%d
", ans2);
// case3 (只填第一列的两个角) 答案乘 2
int ans3 = 0;
// case3.1 最后一列放
ans3 = (ans3 + ((C(n*m-4, k-2) - C(n*(m-1)-2, k-2)) % M + M) % M) % M;
// case3.2 最后一列不放
memset(f, 0, sizeof(f));
for(int i = 0 ; i <= n-2 ; ++i){
if(k <= (n-i)*(m-1)) { // 能放得下
f[i] = 1ll * C(n-2, i) * C((n-i)*(m-1)-2, k-2) % M;
}
if(i & 1){
ans3 = ((ans3 - f[i]) % M + M) % M;
} else{
ans3 = (ans3 + f[i]) % M;
}
}
// printf("%d
", ans3);
ans = (ans + 2ll * ans3 % M) % M;
// case4 (只放一个角) 答案乘 4
int ans4 = 0;
// case4.1 两排都放
ans4 = (ans4 + ((((C(n*m-4, k-1) - C((n-1)*m-2, k-1))%M - C(n*(m-1)-2, k-1))%M + C((n-1)*(m-1)-1, k-1))%M+M)%M)%M;
// case4.2 只有最后一列放
int res1 = 0, res2 = 0;
memset(f, 0, sizeof(f)); // 随便放满足最后一行都被占领的方案数
for(int i = 0 ; i <= m-2 ; ++i){
if(k <= (n-1)*(m-i)-1) { // 能放得下
f[i] = 1ll * C(m-2, i) * C((n-1)*(m-i)-2, k-1) % M;
}
if(i & 1){
res1 = ((res1 - f[i]) % M + M) % M;
} else{
res1 = (res1 + f[i]) % M;
}
}
memset(f, 0, sizeof(f)); // 最后一列不放满足最后一行都被占领的方案数
for(int i = 0 ; i <= m-2 ; ++i){
if(k <= (n-1)*(m-1-i)) { // 能放得下
f[i] = 1ll * C(m-2, i) * C((n-1)*(m-1-i)-1, k-1) % M;
}
if(i & 1){
res2 = ((res2 - f[i]) % M + M) % M;
} else{
res2 = (res2 + f[i]) % M;
}
}
ans4 = (ans4 + ((res1 - res2) % M + M) % M) % M;
// case4.2 只有最后一行放
res1 = 0, res2 = 0;
memset(f, 0, sizeof(f)); // 随便放满足最后一列都被占领的方案数
for(int i = 0 ; i <= n-2 ; ++i){
if(k <= (m-1)*(n-i)-1) { // 能放得下
f[i] = 1ll * C(n-2, i) * C((m-1)*(n-i)-2, k-1) % M;
}
if(i & 1){
res1 = ((res1 - f[i]) % M + M) % M;
} else{
res1 = (res1 + f[i]) % M;
}
}
memset(f, 0, sizeof(f)); // 最后一行不放满足最后一列都被占领的方案数
for(int i = 0 ; i <= n-2 ; ++i){
if(k <= (m-1)*(n-1-i)) { // 能放得下
f[i] = 1ll * C(n-2, i) * C((m-1)*(n-1-i)-1, k-1) % M;
}
if(i & 1){
res2 = ((res2 - f[i]) % M + M) % M;
} else{
res2 = (res2 + f[i]) % M;
}
}
ans4 = (ans4 + ((res1 - res2) % M + M) % M) % M;
// printf("%d
", ans4);
ans = (ans + 2ll * ans4 % M * 2 % M) % M;
// case5 (角全都不放)
int ans5 = 0;
// case5.1 两行两列都放
ans5 = (ans5 + (((C(n*m-4, k) - 1ll*2*C((n-1)*m-2, k)%M)%M - 1ll*2*C(n*(m-1)-2, k)%M)%M+M)%M)%M; // 随便放-至少一排不放-至少一列不放
ans5 = (ans5 + ((1ll*2*C((n-1)*(m-1)-1, k)%M*2%M + C(n*(m-2), k))%M + C((n-2)*m, k))%M)%M; // +至少一行一列不放+至少两行不放+至少两列不放
ans5 = (ans5 + ((-1ll*2*C((n-2)*(m-1), k)%M - 1ll*2*C((n-1)*(m-2), k)%M)%M+M)%M)%M; // -至少两行一列不放-至少一行两列不放
ans5 = (ans5 + C((n-2)*(m-2), k))%M; // + 至少两行两列不放
// case5.2 放两行一列 答案乘 2
int res = 0;
res1 = 0;
memset(f, 0, sizeof(f)); // +随便放满足最后一列都被占领的方案数
for(int i = 0 ; i <= n-2 ; ++i){
if(k <= (n-i)*(m-1)-2) { // 能放得下
f[i] = 1ll * C(n-2, i) * C((m-1)*(n-i)-2, k) % M;
}
if(i & 1){
res1 = ((res1 - f[i]) % M + M) % M;
} else{
res1 = (res1 + f[i]) % M;
}
}
res = (res + res1) % M;
res1 = 0;
memset(f, 0, sizeof(f)); // -有一行不放满足最后一列都被占领的方案数 * 2
for(int i = 0 ; i <= n-2 ; ++i){
if(k <= (n-1-i)*(m-1)-1) { // 能放得下
f[i] = 1ll * C(n-2, i) * C((n-1-i)*(m-1)-1, k) % M;
}
if(i & 1){
res1 = ((res1 - f[i]) % M + M) % M;
} else{
res1 = (res1 + f[i]) % M;
}
}
res = ((res - 2ll * res1 % M) % M + M) % M;
res1 = 0;
memset(f, 0, sizeof(f)); // -有一列不放满足最后一列都被占领的方案数
for(int i = 0 ; i <= n-2 ; ++i){
if(k <= (n-i)*(m-2)) { // 能放得下
f[i] = 1ll * C(n-2, i) * C((n-i)*(m-2), k) % M;
}
if(i & 1){
res1 = ((res1 - f[i]) % M + M) % M;
} else{
res1 = (res1 + f[i]) % M;
}
}
res = ((res - res1)% M + M) % M;
res1 = 0;
memset(f, 0, sizeof(f)); // +两行都不放满足最后一列都被占领的方案数
for(int i = 0 ; i <= n-2 ; ++i){
if(k <= (n-2-i)*(m-1)) { // 能放得下
f[i] = 1ll * C(n-2, i) * C((n-2-i)*(m-1), k) % M;
}
if(i & 1){
res1 = ((res1 - f[i]) % M + M) % M;
} else{
res1 = (res1 + f[i]) % M;
}
}
res = (res + res1) % M;
res1 = 0;
memset(f, 0, sizeof(f)); // +一行一列都不放满足最后一列都被占领的方案数 * 2
for(int i = 0 ; i <= n-2 ; ++i){
if(k <= (n-1-i)*(m-2)) { // 能放得下
f[i] = 1ll * C(n-2, i) * C((n-1-i)*(m-2), k) % M;
}
if(i & 1){
res1 = ((res1 - f[i]) % M + M) % M;
} else{
res1 = (res1 + f[i]) % M;
}
}
res = (res + 2ll * res1 % M) % M;
res1 = 0;
memset(f, 0, sizeof(f)); // -两行一列都不放满足最后一列都被占领的方案数
for(int i = 0 ; i <= n-2 ; ++i){
if(k <= (n-2-i)*(m-2)) { // 能放得下
f[i] = 1ll * C(n-2, i) * C((n-2-i)*(m-2), k) % M;
}
if(i & 1){
res1 = ((res1 - f[i]) % M + M) % M;
} else{
res1 = (res1 + f[i]) % M;
}
}
res = ((res - res1) % M + M) % M;
ans5 = (ans5 + 2ll * res % M) % M;
// case5.3 放两列一行 答案乘 2
res = 0;
res1 = 0;
memset(f, 0, sizeof(f)); // +随便放满足最后一列都被占领的方案数
for(int i = 0 ; i <= m-2 ; ++i){
if(k <= (m-i)*(n-1)-2) { // 能放得下
f[i] = 1ll * C(m-2, i) * C((n-1)*(m-i)-2, k) % M;
}
if(i & 1){
res1 = ((res1 - f[i]) % M + M) % M;
} else{
res1 = (res1 + f[i]) % M;
}
}
res = (res + res1) % M;
res1 = 0;
memset(f, 0, sizeof(f)); // -有一行不放满足最后一列都被占领的方案数 * 2
for(int i = 0 ; i <= m-2 ; ++i){
if(k <= (m-1-i)*(n-1)-1) { // 能放得下
f[i] = 1ll * C(m-2, i) * C((m-1-i)*(n-1)-1, k) % M;
}
if(i & 1){
res1 = ((res1 - f[i]) % M + M) % M;
} else{
res1 = (res1 + f[i]) % M;
}
}
res = ((res - 2ll * res1 % M) % M + M) % M;
res1 = 0;
memset(f, 0, sizeof(f)); // -有一列不放满足最后一列都被占领的方案数
for(int i = 0 ; i <= m-2 ; ++i){
if(k <= (m-i)*(n-2)) { // 能放得下
f[i] = 1ll * C(m-2, i) * C((m-i)*(n-2), k) % M;
}
if(i & 1){
res1 = ((res1 - f[i]) % M + M) % M;
} else{
res1 = (res1 + f[i]) % M;
}
}
res = ((res - res1)% M + M) % M;
res1 = 0;
memset(f, 0, sizeof(f)); // +两行都不放满足最后一列都被占领的方案数
for(int i = 0 ; i <= m-2 ; ++i){
if(k <= (m-2-i)*(n-1)) { // 能放得下
f[i] = 1ll * C(m-2, i) * C((m-2-i)*(n-1), k) % M;
}
if(i & 1){
res1 = ((res1 - f[i]) % M + M) % M;
} else{
res1 = (res1 + f[i]) % M;
}
}
res = (res + res1) % M;
res1 = 0;
memset(f, 0, sizeof(f)); // +一行一列都不放满足最后一列都被占领的方案数 * 2
for(int i = 0 ; i <= m-2 ; ++i){
if(k <= (m-1-i)*(n-2)) { // 能放得下
f[i] = 1ll * C(m-2, i) * C((m-1-i)*(n-2), k) % M;
}
if(i & 1){
res1 = ((res1 - f[i]) % M + M) % M;
} else{
res1 = (res1 + f[i]) % M;
}
}
res = (res + 2ll * res1 % M) % M;
res1 = 0;
memset(f, 0, sizeof(f)); // -两行一列都不放满足最后一列都被占领的方案数
for(int i = 0 ; i <= m-2 ; ++i){
if(k <= (m-2-i)*(n-2)) { // 能放得下
f[i] = 1ll * C(m-2, i) * C((m-2-i)*(n-2), k) % M;
}
if(i & 1){
res1 = ((res1 - f[i]) % M + M) % M;
} else{
res1 = (res1 + f[i]) % M;
}
}
res = ((res - res1) % M + M) % M;
ans5 = (ans5 + 2ll * res % M) % M;
// case5.4 放两列
res = 0;
res1 = 0;
memset(f, 0, sizeof(f)); // +随便放满足上下两行都被占领的方案数
for(int i = 0 ; i <= m-2 ; ++i){
if(k <= (m-i)*(n-2)) { // 能放得下
f[i] = 1ll * C(m-2, i) * C((m-i)*(n-2), k) % M;
}
if(i & 1){
res1 = ((res1 - f[i]) % M + M) % M;
} else{
res1 = (res1 + f[i]) % M;
}
}
res = (res + res1) % M;
res1 = 0;
memset(f, 0, sizeof(f)); // -一列不放满足上下两行都被占领的方案数 * 2
for(int i = 0 ; i <= m-2 ; ++i){
if(k <= (m-1-i)*(n-2)) { // 能放得下
f[i] = 1ll * C(m-2, i) * C((m-1-i)*(n-2), k) % M;
}
if(i & 1){
res1 = ((res1 - f[i]) % M + M) % M;
} else{
res1 = (res1 + f[i]) % M;
}
}
res = ((res - 2ll*res1%M) % M + M) % M;
res1 = 0;
memset(f, 0, sizeof(f)); // +两列都不放满足上下两行都被占领的方案数 * 2
for(int i = 0 ; i <= m-2 ; ++i){
if(k <= (m-2-i)*(n-2)) { // 能放得下
f[i] = 1ll * C(m-2, i) * C((m-2-i)*(n-2), k) % M;
}
if(i & 1){
res1 = ((res1 - f[i]) % M + M) % M;
} else{
res1 = (res1 + f[i]) % M;
}
}
res = (res + res1) % M;
ans5 = (ans5 + res) % M;
// case5.5 放两行
res = 0;
res1 = 0;
memset(f, 0, sizeof(f)); // +随便放满足上下两行都被占领的方案数
for(int i = 0 ; i <= n-2 ; ++i){
if(k <= (n-i)*(m-2)) { // 能放得下
f[i] = 1ll * C(n-2, i) * C((n-i)*(m-2), k) % M;
}
if(i & 1){
res1 = ((res1 - f[i]) % M + M) % M;
} else{
res1 = (res1 + f[i]) % M;
}
}
res = (res + res1) % M;
res1 = 0;
memset(f, 0, sizeof(f)); // -一列不放满足上下两行都被占领的方案数 * 2
for(int i = 0 ; i <= n-2 ; ++i){
if(k <= (n-1-i)*(m-2)) { // 能放得下
f[i] = 1ll * C(n-2, i) * C((n-1-i)*(m-2), k) % M;
}
if(i & 1){
res1 = ((res1 - f[i]) % M + M) % M;
} else{
res1 = (res1 + f[i]) % M;
}
}
res = ((res - 2ll*res1%M) % M + M) % M;
res1 = 0;
memset(f, 0, sizeof(f)); // +两列都不放满足上下两行都被占领的方案数 * 2
for(int i = 0 ; i <= n-2 ; ++i){
if(k <= (n-2-i)*(m-2)) { // 能放得下
f[i] = 1ll * C(n-2, i) * C((n-2-i)*(m-2), k) % M;
}
if(i & 1){
res1 = ((res1 - f[i]) % M + M) % M;
} else{
res1 = (res1 + f[i]) % M;
}
}
res = (res + res1) % M;
ans5 = (ans5 + res) % M;
ans = (ans + ans5) % M;
printf("%d
", ans);
}
return 0;
}