BZOJ 2303
题意:有一个$n \times m$的矩阵,可以放入$0$或$1$,现在已经有$k$个格子放好了$0$或$1$,要把矩阵放满,要求是矩阵中每个$2 \times 2$的格子有奇数个$1$。输入$n、m$和已经放了的格子,求方案数。
根据异或性质$a \oplus b \oplus a = b$
考虑一个矩阵
ABCD
EFGH
IJKL
我们发现对于任意$2 \times 2$矩形异或和都是$1$,因为要符合题意
那么我们发现
$$
A \oplus B \oplus E \oplus F=1 \tag{1}
$$
$$
B \oplus C \oplus F \oplus G=1 \tag{2}
$$
$(1), (2)$异或,则得
$$
A \oplus E \oplus C \oplus G=0 \tag{3}
$$
又可以得到
$$
C \oplus G \oplus D \oplus H = 1 \tag{4}
$$
$(3), (4)$异或,则得
$$
A \oplus E \oplus D \oplus H = 1
$$
我们发现横向异或可以数学归纳法证明,$(1,1) \to (i, j)$, 如果列数是偶数,则异或和为1,否则为0
对于列同理
那么我们发现只要行列都是偶数,那么异或和是1,否则是0
也可以直观上来看,对于$(1,1) \to (i, j)$这个矩形,考虑矩形包含$2 \times 2$矩形的个数。
根据数学归纳法,如果原方格内的第一行和第一列的格子被确定,那么整个方格即可被唯一确定。
我们可以发现$(1,1) \oplus (i,1) \oplus (1,j) \oplus (i,j)=0 / 1$ (取决于上述规则)
那么我们就可以枚举$(1,1)$填什么(如果没有限定,否则就只填一种),然后维护异或关系。
显然可以用带权并查集来做。具体看代码实现。最后答案是除了$(1,1)$联通块的个数$k$, $2^k$
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<iostream>
#include<cmath>
#include<vector>
#include<queue>
#define ms(i, j) memset(i, j, sizeof i)
#define LL long long
#define db double
#define fir first
#define sec second
#define mp make_pair
using namespace std;
namespace flyinthesky {
const int MAXN = 200000 + 5, MO = 1e9;
int n, m, k, x[MAXN], y[MAXN], c[MAXN];
int f[MAXN], g[MAXN]; // g[i] 是 i 和 rt 的关系
int find(int x) {
if (x == f[x]) return x;
int rt = find(f[x]);
g[x] ^= g[f[x]];
return f[x] = rt;
}
void mge(int a, int b, int ans) { // 带权并查集
int x = find(a), y = find(b);
if (x != y) {
f[x] = y;
g[x] = g[a] ^ g[b] ^ ans;
}
}
LL ksm(LL a, LL b) {
LL ans = 1, bs = a;
while (b) {
if (b & 1) ans = (ans * bs) % MO;
bs = (bs * bs) % MO;
b >>= 1;
}
return ans;
}
LL work(int whw) {
for (int i = 0; i <= n + m; ++i) f[i] = i, g[i] = 0; f[n + 1] = 1; // n + 1, 1 是同一个位置
for (int i = 1; i <= k; ++i) {
int tx = x[i], ty = y[i];
if (tx == 1 && ty == 1) continue ;
if (tx % 2 == 0 && ty % 2 == 0) { // 行列均偶
if (whw ^ c[i] ^ 1) {
if (find(tx) == find(ty + n) && (g[tx] ^ g[ty + n]) != 1) return 0;
mge(tx, ty + n, 1);
} else {
if (find(tx) == find(ty + n) && (g[tx] ^ g[ty + n]) != 0) return 0;
mge(tx, ty + n, 0);
}
} else {
if (whw ^ c[i]) {
if (find(tx) == find(ty + n) && (g[tx] ^ g[ty + n]) != 1) return 0;
mge(tx, ty + n, 1);
} else {
if (find(tx) == find(ty + n) && (g[tx] ^ g[ty + n]) != 0) return 0;
mge(tx, ty + n, 0);
}
}
}
int tot = 0;
for (int i = 1; i <= n + m; ++i) if (find(i) == i) ++tot; // 找连通块
return ksm(2ll, tot - 1);
}
void clean() {
}
int solve() {
clean();
cin >> n >> m >> k;
int fl = -1;
for (int i = 1; i <= k; ++i) {
scanf("%d%d%d", &x[i], &y[i], &c[i]);
if (x[i] == 1 && y[i] == 1) fl = c[i]; // 1 被确定
}
if (fl == -1) {
printf("%lld\n", (work(1) + work(0)) % MO);
} else printf("%lld\n", work(fl));
return 0;
}
}
int main() {
flyinthesky::solve();
return 0;
}
//
/*
1 1 1
1 1 0
*/