Gifts from Knowledge
Gifts from Knowledge
异或带权并查集
问题简述
给定一个 \(r\) 行 \(c\) 列的二进制矩阵。每一行可以选择翻转或不翻转。翻转一行表示将该行的元素左右颠倒。
要求选择一组行进行翻转,使得翻转后每一列中至多有一个 \(1\)。
求方案数,对 \(10^9+7\) 取模。两个方案不同当且仅当存在某一行在一个方案中翻转、在另一个方案中不翻转。
核心观察
翻转第 \(i\) 行会交换第 \(j\) 列和第 \(c-1-j\) 列。因此可以把矩阵的列按对称位置成对处理:
当 \(c\) 为奇数时,最中间一列单独处理,因为翻转不会改变该列。
对于每一行,我们只关心它在一对对称列 \((L,R)\) 上的两个字符:
00:翻转前后都没有 \(1\),不影响任何列;11:翻转前后两列都有 \(1\),这一行固定占用两列;01或10:翻转会决定这个 \(1\) 最终落在左列还是右列,称为“异位行”。
每个列对最多容纳两个 \(1\):左列一个、右列一个。
足迹统计
设 \(m=c\)。定义
也就是说,\(\text{colcnt}[p]\) 统计的是对称列对 \((p,\ m-1-p)\) 上的 \(1\) 的总数。
在代码中,每读到一个 \(1\),同时加到它所在的列和它的对称列:
因为每一列最多只能有一个 \(1\),所以每个对称列对最多容纳两个 \(1\)。于是有:
- 若存在 \(p\) 使得 \(\text{colcnt}[p]\ge 3\),则无解;
- 若总 \(1\) 的个数 \(w>m\),由鸽笼原理也无解。
另外,当 \(c\) 为奇数时,中间列满足 \(m-1-j=j\),所以中间列的 \(1\) 会被加两次:
- 中间列有 \(1\) 个 \(1\):\(\text{colcnt}[j]=2\),合法;
- 中间列有 \(2\) 个 \(1\):\(\text{colcnt}[j]=4\ge 3\),无解。
异位行之间的异或约束
对于每个对称列对,记录第一次在该列对上出现 \(1\) 的行号:
pos[j] = pos[m - j - 1] = i;
当另一行 \(i\) 也在同一个列对上有 \(1\) 时,建立约束:
其中
这里 \(x_i\in\{0,1\}\) 表示第 \(i\) 行是否翻转。
这个约束的含义如下:
- 若 \(y=1\),说明两行在同一个位置 \(j\) 上都有 \(1\)。翻转后它们仍然在同一列冲突,因此两行必须一翻一不翻,即 \(x_i\oplus x_{\text{pos}[j]}=1\);
- 若 \(y=0\),说明两行在对称列的两侧各有一个 \(1\)。翻转后必须保持一左一右,因此两行必须同翻或同不翻,即 \(x_i\oplus x_{\text{pos}[j]}=0\)。
所有这样的约束都可以用带权并查集维护。
特殊情况的覆盖
1. 一个 11 行
11 行会在两个对称列上各产生一个 \(1\)。扫描到它时,pos[L] 和 pos[R] 都被设为同一行。扫描到第二个 \(1\) 时,pos 已经等于当前行,因此不会建立自约束。
此时该列对的 \(\text{colcnt}=2\),合法。若同一列对还有其他 \(1\),则 \(\text{colcnt}\ge 3\),会被无解条件拦住。
2. 两个 11 行
同一列对上有两个 11 行时,\(\text{colcnt}=4\ge 3\),直接无解。
3. 中间列
中间列翻转后不变,最多只能有一个 \(1\)。若有两个 \(1\),则 \(\text{colcnt}=4\ge 3\),无解。
因此,11 行、00 行、中间列的情况都被“\(\text{colcnt}\le 2\)”这个条件自动覆盖。
带权并查集
并查集中维护每个变量到根节点的异或值。unite(x, y, lim) 表示加入约束:
若 \(x\) 和 \(y\) 已经在同一连通块中,则检查当前异或值是否等于 \(lim\);若不等,说明矛盾,无解。
若不在同一连通块,则合并两个连通块,并维护新的异或关系。
所有约束处理完后,若没有矛盾,设并查集中连通块的数量为 \(cnt\)。每个连通块有一个自由变量,可以任选 \(0\) 或 \(1\),因此答案为:
复杂度
设矩阵大小为 \(r\times c\)。
- 扫描矩阵:\(O(rc)\);
- 并查集操作:\(O(r\alpha(r))\)。
总时间复杂度为:
题目保证所有测试用例的 \(\sum r\times c\le 10^6\),因此该复杂度足够。
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
const ll MOD = 1e9 + 7;
struct DSU {
int n;
vector<int> pa, sz, d;
DSU(int _n) {init(_n);}
void init(int _n) {
n = _n;
pa.assign(_n + 1, 0);
sz.assign(_n + 1, 1);
d.assign(_n + 1, 0);
iota(pa.begin(), pa.end(), 0);
}
int findu(int x) {
if(pa[x] == x) return pa[x];
int r = findu(pa[x]);
d[x] ^= d[pa[x]];
pa[x] = r;
return r;
}
bool unite(int x, int y, int lim) {//lim约束,x与y的翻转关系
int tx = findu(x), ty = findu(y);
if(tx == ty) return d[x] ^ d[y] == lim;
int k = d[x] ^ d[y] ^ lim;
if(sz[tx] >= sz[ty]) {
sz[tx] += sz[ty];
pa[ty] = tx;
d[ty] = k;
}
else {
sz[ty] += sz[tx];
pa[tx] = ty;
d[tx] = k;
}
return true;
}
};
ll modpow(ll a, ll b) {
ll r = 1;
while(b) {
if(b & 1) r = r * a % MOD;
a = a * a % MOD;
b >>= 1;
}
return r;
}
void solve() {
int n, m; cin >> n >> m;
vector<string> g(n + 1);
for(int i = 0; i < n; i ++) cin >> g[i];
vector<int> colcnt(m + 1);
int w = 0;
for(int i = 0; i < n; i ++) {
for(int j = 0; j < m; j ++) {
colcnt[j] += g[i][j] - '0';
colcnt[m - j - 1] += g[i][j] - '0';
w += g[i][j] - '0';
}
}
if(w > m) {cout << "0\n"; return;}
for(int j = 0; j < m; j ++) {
if(colcnt[j] >= 3) {cout << "0\n"; return;}
}
DSU dsu(n);
vector<int> pos(m + 1, -1);
for(int i = 0; i < n; i ++) {
for(int j = 0; j < m; j ++) {
if(g[i][j] == '0') continue;
if(pos[j] == -1) pos[j] = pos[m - j - 1] = i;
else if(pos[j] != i) {
int y = (g[pos[j]][j] == '1');
if(!dsu.unite(i, pos[j], y)) {cout << "0\n"; return;}
}
}
}
int cnt = 0;
for(int i = 0; i < n; i ++) {
if(dsu.findu(i) == i) cnt ++;
}
ll ans = modpow(2, cnt) % MOD;
cout << ans << "\n";
}
int main() {
ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);
int _ = 1; cin >> _;
while(_--) solve();
return 0;
}

浙公网安备 33010602011771号