Description在一个二维宇宙中,恒星可以用平面上的点 ( x , y ) (x, y) ( x , y ) 表示。当且仅当两颗恒星的 x x x 或 y y y 坐标相同,且它们之间的线段上没有其他恒星时,这两颗恒星直接相连。定义星系为由直接或间接(通过其他恒星)相连的恒星组成的连通分量。
对于一个恒星集合,其价值定义为:经过任意次(可能为零次)以下操作后,能够得到的最小星系数量。每次操作中,你可以选择一个没有恒星的位置 ( x , y ) (x, y) ( x , y ) 。如果在此处创建恒星后,该恒星能够直接连接到至少 3 3 3 颗恒星,则你可以在此处创建一颗新恒星。
给定一个由 0 0 0 和 1 1 1 组成的 n × n n \times n n × n 矩阵 a a a ,描述一个恒星集合 S S S 。当且仅当 a x , y = 1 a_{x,y} = 1 a x , y = 1 时,( x , y ) (x, y) ( x , y ) 处存在恒星。请计算 S S S 的所有非空子集的价值之和,结果对 1 0 9 + 7 10^9 + 7 1 0 9 + 7 取模。
n ≤ 14 n\leq 14 n ≤ 1 4 。
Solution考虑给定集合怎么求答案。
首先有个性质,就是一个连通块假设包含它的最小矩形是 [ x 1 , x 2 ] [ y 1 , y 2 ] [x_1,x_2][y_1,y_2] [ x 1 , x 2 ] [ y 1 , y 2 ] ,那么将连完边后相邻的两个点看成一个线段对 x x x 轴和 y y y 轴分别进行覆盖,则 [ x 1 , x 2 ] [x_1,x_2] [ x 1 , x 2 ] 和 [ y 1 , y 2 ] [y_1,y_2] [ y 1 , y 2 ] 全部会被覆盖到。
证明就考虑每次加入一个点 ( x 0 , y 0 ) (x_0,y_0) ( x 0 , y 0 ) 不妨设 x ∈ [ x 1 , x 2 ] x\in[x_1,x_2] x ∈ [ x 1 , x 2 ] ,则这个点一定能够通过 x x x 的边和连通块连在一起,连过去的边也就能够更新 [ y 1 , y 2 ] [y_1,y_2] [ y 1 , y 2 ] 。
对于两个连通块合并就简单了,两个连通块能合并的当且仅当两个矩形的 x x x 或 y y y 存在交。
根据上面的过程,只求一次答案可以对于每个初始点 ( x i , y i ) (x_i,y_i) ( x i , y i ) ,建立 [ x i , x i ] [ y i , y i ] [x_i,x_i][y_i,y_i] [ x i , x i ] [ y i , y i ] 的矩形,每次合并矩形即可。
然后考虑怎么做这道题。
设 f l 1 , r 1 , l 2 , r 2 f_{l_1,r_1,l_2,r_2} f l 1 , r 1 , l 2 , r 2 表示将 [ l 1 , r 1 ] [ l 2 , r 2 ] [l_1,r_1][l_2,r_2] [ l 1 , r 1 ] [ l 2 , r 2 ] 内的点合并后恰好构成 [ l 1 , r 1 ] [ l 2 , r 2 ] [l_1,r_1][l_2,r_2] [ l 1 , r 1 ] [ l 2 , r 2 ] 的方案数。
转移就考虑用连通块数至少为 1 1 1 的方案数减去合并后不是 [ l 1 , r 1 ] [ l 2 , r 2 ] [l_1,r_1][l_2,r_2] [ l 1 , r 1 ] [ l 2 , r 2 ] 的方案数。
先枚举 x x x 的左端点最小的矩形 [ l 1 ′ , r 1 ′ ] [ l 2 ′ , r 2 ′ ] [l_1',r_1'][l_2',r_2'] [ l 1 ′ , r 1 ′ ] [ l 2 ′ , r 2 ′ ] ,然后需要保证 [ r 1 ′ + 1 , r 1 ] [ l 2 , r 2 ] [r_1'+1,r_1][l_2,r_2] [ r 1 ′ + 1 , r 1 ] [ l 2 , r 2 ] 构成的所有矩形的 y y y 区间不能和 [ l 2 ′ , r 2 ′ ] [l_2',r_2'] [ l 2 ′ , r 2 ′ ] 有交,现有状态无法转移,需要设新状态。
设 g l , r , S g_{l,r,S} g l , r , S 表示 x x x 坐标在 [ l , r ] [l,r] [ l , r ] ,y y y 坐标属于 S S S 集合的点连出的所有连通块只能在 [ l , r ] S [l,r]S [ l , r ] S 这个子集里的方案数。
对于 f f f 的转移可以直接用 g g g 维护,而 g g g 的转移就考虑分讨 x = l x=l x = l 是否有点,如果没有,贡献为 g l + 1 , r , S g_{l+1,r,S} g l + 1 , r , S ;否则就要枚举这个 x x x 等于 l l l 的矩形的具体形状,然后同样可以转移。
而最终要输出的是连通块数,再维护 h l , r , S h_{l,r,S} h l , r , S 表示 x x x 坐标在 [ l , r ] [l,r] [ l , r ] ,y y y 坐标属于 S S S 集合的点连出的所有连通块只能在 [ l , r ] S [l,r]S [ l , r ] S 这个子集里的所有方案的连通块数之和。转移和 g g g 同理。
时间复杂度:O ( n 8 + 2 n n 4 ) O(n^8+2^nn^4) O ( n 8 + 2 n n 4 ) 。
Code1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 #include <bits/stdc++.h> const int kMaxN = 16 , kMaxS = (1 << 14 ) + 5 , kMod = 1e9 + 7 ;int n;int f[kMaxN][kMaxN][kMaxN][kMaxN], g[kMaxN][kMaxN][kMaxS], h[kMaxN][kMaxN][kMaxS], cnt[kMaxN][kMaxN], pw[kMaxN * kMaxN];bool a[kMaxN][kMaxN];int qpow (int bs, int64_t idx = kMod - 2 ) { int ret = 1 ; for (; idx; idx >>= 1 , bs = (int64_t )bs * bs % kMod) if (idx & 1 ) ret = (int64_t )ret * bs % kMod; return ret; }inline int add (int x, int y) { return (x + y >= kMod ? x + y - kMod : x + y); }inline int sub (int x, int y) { return (x >= y ? x - y : x - y + kMod); }inline void inc (int &x, int y) { (x += y) >= kMod ? x -= kMod : x; }inline void dec (int &x, int y) { (x -= y) < 0 ? x += kMod : x; }void clear () { for (int i = 1 ; i <= n; ++i) { for (int j = 1 ; j <= n; ++j) { cnt[i][j] = 0 ; for (int k = 1 ; k <= n; ++k) for (int l = 1 ; l <= n; ++l) f[i][j][k][l] = 0 ; std::fill_n (g[i][j], 1 << n, 0 ); std::fill_n (h[i][j], 1 << n, 0 ); } } }int getcnt (int l1, int r1, int l2, int r2) { return cnt[r1][r2] - cnt[l1 - 1 ][r2] - cnt[r1][l2 - 1 ] + cnt[l1 - 1 ][l2 - 1 ]; }int getst (int l, int r) { if (l > r) return 0 ; else return ((1 << r) - 1 ) ^ ((1 << (l - 1 )) - 1 ); }void dickdreamer () { std::cin >> n; clear (); for (int i = 1 ; i <= n; ++i) { std::string str; std::cin >> str; for (int j = 1 ; j <= n; ++j) { a[i][j] = str[j - 1 ] - '0' ; cnt[i][j] = cnt[i][j - 1 ] + cnt[i - 1 ][j] - cnt[i - 1 ][j - 1 ] + a[i][j]; } } pw[0 ] = 1 ; for (int i = 1 ; i <= n * n; ++i) pw[i] = 2ll * pw[i - 1 ] % kMod; for (int len1 = 1 ; len1 <= n; ++len1) { for (int l1 = 1 ; l1 <= n - len1 + 1 ; ++l1) { int r1 = l1 + len1 - 1 ; for (int len2 = 1 ; len2 <= n; ++len2) { for (int l2 = 1 ; l2 <= n - len2 + 1 ; ++l2) { int r2 = l2 + len2 - 1 ; f[l1][r1][l2][r2] = pw[getcnt (l1, r1, l2, r2)] - 1 ; for (int _l1 = l1; _l1 <= r1; ++_l1) { for (int _r1 = _l1; _r1 <= r1; ++_r1) { for (int _l2 = l2; _l2 <= r2; ++_l2) { for (int _r2 = _l2; _r2 <= r2; ++_r2) { if (_l1 == l1 && _r1 == r1 && _l2 == l2 && _r2 == r2) continue ; if (_r1 == r1) dec (f[l1][r1][l2][r2], f[_l1][_r1][_l2][_r2]); else dec (f[l1][r1][l2][r2], 1ll * f[_l1][_r1][_l2][_r2] * g[_r1 + 1 ][r1][getst (l2, r2) ^ getst (_l2, _r2)] % kMod); } } } } } } for (int s = 0 ; s < (1 << n); ++s) { if (len1 == 1 ) { inc (g[l1][r1][s], 1 ); } else { inc (g[l1][r1][s], g[l1 + 1 ][r1][s]); inc (h[l1][r1][s], h[l1 + 1 ][r1][s]); } for (int p = l1; p <= r1; ++p) { for (int x = 1 ; x <= n; ++x) { for (int y = x; y <= n; ++y) { if (~s >> (y - 1 ) & 1 ) break ; if (p == r1) { inc (g[l1][r1][s], f[l1][p][x][y]); inc (h[l1][r1][s], f[l1][p][x][y]); } else { inc (g[l1][r1][s], 1ll * f[l1][p][x][y] * g[p + 1 ][r1][s ^ getst (x, y)] % kMod); inc (h[l1][r1][s], 1ll * f[l1][p][x][y] * add (g[p + 1 ][r1][s ^ getst (x, y)], h[p + 1 ][r1][s ^ getst (x, y)]) % kMod); } } } } } } } std::cout << h[1 ][n][getst (1 , n)] << '\n' ; }int32_t main () {#ifdef ORZXKR freopen ("in.txt" , "r" , stdin); freopen ("out.txt" , "w" , stdout);#endif std::ios::sync_with_stdio (0 ), std::cin.tie (0 ), std::cout.tie (0 ); int T = 1 ; std::cin >> T; while (T--) dickdreamer (); return 0 ; }