题目链接

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\) 的矩形可以一格格地移动,可以发现这里有大量相等的关系,具体来说:

Topcoder 13986 SubRectangles_时间复杂度

由于图中的四个 \(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\) 行上 “行相同的位置” 有几个。

  1. 先算 “列相同的情况”,即同一行的可以随便填,每一列枚举对应的位置(注意只考虑 “列相同” 的这 \(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} \]

  2. 对于 “行相同的情况”,这里需要容斥,即在计算时要减去可以作为 “列相同” 的情况。用 \(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;
}