题述:对于一个n*m的广场,共有n*m个格子,要放k个东西,每个格子上只能放一个东西且第一行、最后一行、第一列和最后一列上都必须至少放一个东西。在四个顶点的格子相当于同时占了一行和一列。
思路:训练赛的时候第一反应是也许可以枚举所有的情况,但稍微一想发现那是不现实的,就转去做其他题目了,结果一个dfs因为姿势不对tle到无奈,所以最后没时间写这道题。赛后仔细想了想,又结合网上的文字叙述自己推了一遍。这道题情况太多故只能考虑他的相反情况,最后用所有放法减去他的所有相反情况。相反情况就是第一行或最后一行或第一列或最后一列上不能放东西,说到或应该很容易想到得用容斥原理,分别用0 1 2 3代表第一行不放、第一列不放、最后一行不放、最后一列不放,四个元素可以用0~16枚举其所有情况,0000代表四个都放,1111代表四个都不放。容斥原理是加上奇数个减去偶数个,这里是要减去这些相反情况,故是减去奇数个,加上偶数个。
代码如下:
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#include<queue>
#include<cstdlib>
#include<sstream>
#include<deque>
#include<stack>
#include<set>
#include<map>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
const double eps = 1e-6;
const int maxn = 400 + 5;
const int mod = 1000007;
const int dx[] = {1, -1, 0, 0, -1, -1, 1, 1};
const int dy[] = {0, 0, -1, 1, -1, 1, -1, 1};
const int Dis[] = {-1, 1, -5, 5};
const double inf = 0x3f3f3f3f;
int n, m, k;
ll c[maxn][maxn];
void init(){
c[0][0] = 1;
for(int i = 1; i < maxn; ++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(){
init();
int t, kase = 0;
scanf("%d", &t);
while(t--){
scanf("%d%d%d", &n, &m, &k);
vector<int> vec;
ll ans = c[n * m][k];
for(int i = 0; i < 16; ++i){
vec.clear();
for(int j = 0; j < 4; ++j){
if(i & (1 << j)) vec.push_back(j);//将0 1 2 3中不能放东西的加到集合中。
}
int len = vec.size();
ll tmp = 0;
if(len == 1){
if(vec[0] & 1) tmp = c[n * m - n][k];
else tmp = c[n * m - m][k];
}
else if(len == 2){
if((vec[0] & 1) && (vec[1] & 1)) tmp = c[n * (m - 2)][k];
else if(!(vec[0] & 1) && !(vec[1] & 1)) tmp = c[(n - 2) * m][k];
else tmp = c[(n - 1) * (m - 1)][k];
}
else if(len == 3){
if((vec[0] + vec[1] + vec[2]) & 1) tmp = c[(n - 2) * (m - 1)][k];
else tmp = c[(n - 1) * (m - 2)][k];
}
else if(len == 4){
tmp = c[(n - 2) * (m - 2)][k];
}
if(len & 1) ans = (ans - tmp + mod) % mod;
else ans = (ans + tmp) % mod;
}
printf("Case %d: %lld\n", ++kase, ans);
}
return 0;
}