SUBMAT
题目背景:
分析:容斥 + 结论
直接考虑H = W = 4,会出现什么情况,显然,矩阵中存在很多的等量关系,也就是方程,我们大概要做的就是求解方程组的解的个数。考虑到:
由此可以得到:
注意到,对于一个x, y <= 4,必然有,
或
所以,我们可以通过枚举H * W中的哪些格子是向下相等的,哪些格子是向右相等的,显然,向下的和向右的是独立的,那么我们可以通过枚举有多少个1,来直接进行组合数,但是注意到,如果向下相等的,同样满足了向右相等,就会重复,所以需要推导容斥的公式,具体详见代码注释。复杂度O(2HW(H + W)2)
PS:代码中的表格表示枚举容斥当中固定多少位置,获得对应和的方案数。
Source:
/*
created by scarlyw
*/
#include <cstdio>
#include <string>
#include <algorithm>
#include <cstring>
#include <iostream>
#include <cmath>
#include <cctype>
#include <vector>
#include <set>
#include <queue>
#include <ctime>
#include <map>
#include <bitset>
inline char read() {
static const int IN_LEN = 1024 * 1024;
static char buf[IN_LEN], *s, *t;
if (s == t) {
t = (s = buf) + fread(buf, 1, IN_LEN, stdin);
if (s == t) return -1;
}
return *s++;
}
///*
template<class T>
inline void R(T &x) {
static char c;
static bool iosig;
for (c = read(), iosig = false; !isdigit(c); c = read()) {
if (c == -1) return ;
if (c == '-') iosig = true;
}
for (x = 0; isdigit(c); c = read())
x = ((x << 2) + x << 1) + (c ^ '0');
if (iosig) x = -x;
}
//*/
const int OUT_LEN = 1024 * 1024;
char obuf[OUT_LEN], *oh = obuf;
inline void write_char(char c) {
if (oh == obuf + OUT_LEN) fwrite(obuf, 1, OUT_LEN, stdout), oh = obuf;
*oh++ = c;
}
template<class T>
inline void W(T x) {
static int buf[30], cnt;
if (x == 0) write_char('0');
else {
if (x < 0) write_char('-'), x = -x;
for (cnt = 0; x; x /= 10) buf[++cnt] = x % 10 + 48;
while (cnt) write_char(buf[cnt--]);
}
}
inline void flush() {
fwrite(obuf, 1, oh - obuf, stdout);
}
/*
template<class T>
inline void R(T &x) {
static char c;
static bool iosig;
for (c = getchar(), iosig = false; !isdigit(c); c = getchar())
if (c == '-') iosig = true;
for (x = 0; isdigit(c); c = getchar())
x = ((x << 2) + x << 1) + (c ^ '0');
if (iosig) x = -x;
}
//*/
const int mod = 1000000000 + 7;
const int MAXN = 10;
int n, m, h, w;
int c[MAXN][MAXN], row[MAXN], col[MAXN];
inline int mod_pow(int a, int b) {
int ans = 1;
for (; b; b >>= 1, a = (long long)a * a % mod)
if (b & 1) ans = (long long)ans * a % mod;
return ans;
}
inline void solve() {
long long ans = 0;
R(n), R(m), R(h), R(w);
for (int i = 0; i <= 4; ++i) c[i][i] = c[i][0] = 1;
for (int i = 2; i <= 4; ++i)
for (int j = 1; j < i; ++j)
c[i][j] = c[i - 1][j] + c[i - 1][j - 1];
for (int cur = 0, s = (1 << h * w); cur < s; ++cur) {
for (int i = 0; i < 4; ++i) row[i] = col[i] = 0;
long long ret = 1;
for (int i = 0; i < h * w; ++i)
if (cur & (1 << i))
row[i / w]++, col[i % w]++;
/*1表示向下相等, 显然相邻两列的和相等,固定了col[i]个数,其他的数满足
和相等即可,直接枚举和,和方案数乘法原理即可,j为枚举和,z表示一共有z列*/
for (int i = 0; i < w; ++i) {
int z = (m - 1 - i) / w + 1, x = 0;
for (int j = 0; j <= col[i]; ++j)
x = (x + mod_pow(c[col[i]][j], z)) % mod;
ret = ret * x % mod;
}
for (int i = 0; i < h; ++i) row[i] = w - row[i]; // 每一行有多少向右相等
for (int i = 0; i < h; ++i) {
int z = (n - 1 - i) / h + 1, x = 0;
switch (row[i]) {
case 0: x = 1;
// 四个都向下相等,情况相同
break ;
case 1: x = 0;
//显然只有这一个数,那么肯定满足向下和向右
break ;
//以下三张表格,表示枚举容斥当中枚举固定多少位置,
//获得的对应和的方案数
case 2: x = mod_pow(2, z) - 2;
// 和/固定个数 0 1 2
// 0 1 2 1
// 1 2^z 4 2
// 2 1 2 1
break ;
case 3: x = mod_pow(3, z) * 2 % mod -
(long long)mod_pow(2, z) * 6 % mod + 6;
(x < 0) ? (x += mod) : (0);
break ;
// 和/固定个数 0 1 2 3
// 0 1 3 3 1
// 1 3^z 3*2^z+3 9 3
// 2 3^z 3*2^z+3 9 3
// 3 1 3 3 1
case 4: x = (long long)mod_pow(6, z) + 2LL * mod_pow(4, z) % mod
- 16LL * mod_pow(3, z) % mod
+ 24LL * mod_pow(2, z) % mod - 14;
x %= mod, (x < 0) ? (x += mod) : (0);
// 和/固定个数 0 1 2 3 4
// 0 1 4 6 4 1
// 1 4^z 4*3^z+4 6*2^z+12 16 4
// 2 6^z 8*3^z 12*2^z+12 24 6
// 3 4^z 4*3^z+4 6*2^z+12 16 4
// 4 1 4 6 4 1
break ;
}
ret = ret * x % mod;
}
ans = (ans + ret) % mod;
}
std::cout << ans;
}
int main() {
freopen("submat.in", "r", stdin);
freopen("submat.out", "w", stdout);
solve();
return 0;
}