Topcoder 13986 SubRectangles
题目链接
题目大意
你要在一张 \(h\times w\) 的网格图里的一些位置上填 \(1\),满足对于给定 \(n,m\),任意一个 \(n\times m\) 的子矩阵内部 \(1\) 的数量都相同,求方案数对 \(10^9+7\) 取模的值。
\(1\leq h,w\leq 10^9\),\(1\leq n,m\leq 4\)
思路
这些 \(n\times m\) 的矩形可以一格格地移动,可以发现这里有大量相等的关系,具体来说:
由于图中的四个 \(n\times m\) 的矩形 \(1\) 的个数相同,\(a,b,c,d\) 几个位置有关系 \(a+x=b+y,c+x=d+y\),从而 \(d=b+c-a\),也就是说当 \(a,b,c\) 确定时,\(d\) 就被唯一确定了。这一点可以推广,对于 \(i\%n=x_0,\;j\%m=y_0\) 的点我们对其进行重新标号,则有 \(d_{i,j}=d_{i,0}+d_{j,0}-d_{0,0}\),即 \(x\) 和 \(y\) 的区域伸长。
以上的结论说明,当网格图的前 \(n\) 行和前 \(m\) 列被确定时,整个网格图就被唯一确定了。但是要注意,这里可能会有无解的情况,即 \(d_{i,j}=-1\) 或 \(d_{i,j}=2\),这种情况出现在 \(\exists i,j,\;st.d_{i,0}=d_{0,j}=1-d_{0,0}\) 的时候,仔细思考一下,可以得到结论:
- 网格图存在解 \(\iff \forall i,d_{i,0}=d_{0,0}\bigvee \forall j,d_{0,j}=d_{0,0}\)
然后这个题就可以做了,考虑左上角 \(n\times m\) 矩阵的每个位置,它们都会成为 \(d_{0,0}\),所以依次枚举每一位是一行全部相同还是一列全部相同,然后对于每一种情况计算答案最后即可。
具体来说,分别用 \(col_j\) 和 \(row_i\) 表示第 \(j\) 列上 “列相同的位置” 有几个、第 \(i\) 行上 “行相同的位置” 有几个。
-
先算 “列相同的情况”,即同一行的可以随便填,每一列枚举对应的位置(注意只考虑 “列相同” 的这 \(col_i\) 的位置,有点绕,请自行脑补)有几个填 \(1\),接下来后面对应位置列 \(1\) 的个数都要和当前列相同,记 \(num=\lfloor\frac{w-i+m-1}{m}\rfloor\) 为这些列的个数,方案相加后每列求积即可,即
\[\prod_{i=0}^{m-1}\sum_{j=0}^{col_i}\binom{col_i}{j}^{num} \] -
对于 “行相同的情况”,这里需要容斥,即在计算时要减去可以作为 “列相同” 的情况。用 \(row_i=3\) 举例:类似的考虑列(实际上是行,但整体上是前 \(m\) 列)随便填,首先一行(对应位置上)的值不能为 \(0\) 或 \(3\),不然所有位置都要填 \(0/1\),和 “列相同“ 一样了,然后还要考虑某一位置一列下来都一样的情况,该位置也是 ”列相同“ 了,这边也要做个小容斥(\(3\) 个都 ”列相同“),最后的权值即为
\[2\times 3^{num}-6\times 2^{num}+6 \]其它 \(row_i\) 情况类似,由于 \(0\leq row_i\leq 4\),手算就行了。
直接对着实现的时间复杂度是 \(O(n^2\cdot 2^{n^2})\) 的,不过通过合适的预处理可以将其降到 \(O(2^{n^2})\) 。
Code
写的是 \(O(n^2\cdot 2^{n^2})\),能过那就不优化了吧
#include<iostream>
#include<cstring>
#define mem(a,b) memset(a, b, sizeof(a))
#define rep(i,a,b) for(int i = (a); i <= (b); i++)
#define per(i,b,a) for(int i = (b); i >= (a); i--)
#define N 6
#define mod 1000000007
#define ll long long
using namespace std;
class SubRectangles{
public:
int C[N][N], col[N], row[N];
void init(){
mem(C, 0), C[0][0] = 1;
rep(i,1,5){
C[i][0] = 1;
rep(j,1,i) C[i][j] = C[i-1][j]+C[i-1][j-1];
}
}
ll qpow(ll a, int b){
ll ret = 1;
for(; b; b >>= 1){ if(b&1) (ret *= a) %= mod; (a *= a) %= mod; }
return ret;
}
int countWays(int H, int W, int H2, int W2){
init();
int n = H2*W2;
ll ans = 0;
rep(s,0,(1<<n)-1){
mem(row, 0), mem(col, 0);
rep(i,0,n-1) if(s>>i&1)
row[i/W2]++, col[i%W2]++;
ll hor = 1, ver = 1;
rep(i,0,W2-1){
ll num = (W-i+W2-1)/W2, tot = 0;
rep(j,0,col[i]) (tot += qpow(C[col[i]][j], num)) %= mod;
(hor *= tot) %= mod;
}
rep(i,0,H2-1){
ll num = (H-i+H2-1)/H2, tot = 0;
switch(W2-row[i]){
case 0 : tot = 1; break;
case 1 : tot = 0; break;
case 2 : tot = qpow(2, num) - 2; break;
case 3 : tot = 2*qpow(3, num) - 6*qpow(2, num) + 6; break;
case 4 : tot = qpow(6, num) + 2*qpow(4, num) - 16*qpow(3, num) + 24*qpow(2, num) - 14;
}
tot = (tot%mod+mod)%mod;
(ver *= tot) %= mod;
}
(ans += hor*ver%mod) %= mod;
}
return int(ans+mod)%mod;
}
} solve;
int main(){
int a, b, c, d;
cin>>a>>b>>c>>d;
cout<< solve.countWays(a, b, c, d) <<endl;
return 0;
}