P3631 [APIO2011]方格染色

写这题解就离谱...

看上去就好神仙啊,考虑dp怎么解决?不太行,因为只有20pts

于是我们考虑找找规律,能发现相当于四个点如果为红色就是1,为蓝色就是0,那么异或起来值为1

然后这个结论由于异或显然有拓展性

XZU
YWI

X^Y^Z^W=Z^W^U^I=1

X^Y^U^I=0!

那么好像我们可以通过类似于平移一块2*2地毯的形式去不断的得到新的这样的关系

然后又找到了新结论:确定第一行第一列就确定了整个矩阵

证明显然吧,自己推一推?

然后相当于我们要计算第一行第一列的填色方案,于是考虑用这个东西找到规律

如果对于矩阵1,1->x,y,x,y中有一个为偶数那么异或值为0,否则为1

这个可以考虑这个矩阵中有多少个2*2得知

然后有了这个结论我们就可以考虑通过枚举1,1的取值来缩点得到几个连通块进而计算出答案

同一连通块的值确地一个就确定了整块,所以就为2^连通块数是答案

然后注意这样我们的给出的(x,y)是用于缩连通块的,而且他的取值决定了这一切是否合法

是否合法的部分可以用带权并查集去解决

你又会发现我们可以通过增加一行0来解决枚举1,1取值,不过没什么必要,只是更好写

而且我们之前结论还要变一下...

有一个坑点是模数是1e9不是质数...

code

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int P = 1e9;
const long long inv2 = (P) / 2;
const int MAXN = 1e6 + 7;
int f[MAXN], op[MAXN], val[MAXN];
inline int getf(int x) {
	return f[x] == x ? x : f[x] = getf(f[x]);
}
inline int merge(int x, int y, int z) {
	int f1 = getf(x);
	int f2 = getf(y);
	if(z) {
		if(f1 == f2)return 0;
		if(f1 == op[f2])return 1;
		f[f1] = op[f2];
		f[op[f1]] = f2;
	} else {
		if(f1 == op[f2])return 0;
		if(f1 == f2)return 1;

		f[f1] = f2;
		f[op[f1]] = op[f2];
	}
	return 1;
}
long long ksm(long long x, long long y) {
	long long ans = 1;
	while(y) {
		if(y & 1)ans = ans * x % P;
		x = x * x % P;
		y >>= 1;
	}
	return ans;
}
int n, m, k;
int main() {
	scanf("%d%d%d", &n, &m, &k);
	int qwq = n + m;
	for(int i = 1; i <= qwq; ++i) {
		f[i] = op[i + qwq] = i;
		f[i + qwq] = op[i] = i + qwq;
		//扩展域并查集
	}
	for(int i = 1, x, y, z; i <= k; ++i) {
		scanf("%d%d%d", &x, &y, &z);
		if(((x + 1 & 1) == 0) && (y + 1 & 1) == 0)z ^= 1;
		y += n;
		if(!merge(x, y, z)) {
			puts("0");
			return 0;
		}
	}
	// puts("qwq");
	long long ans = 0;
	int st = getf(1);
	val[op[st]] = 1;
	val[st] = 1;
	for(int i = 2; i <= qwq; ++i) {
		int p = getf(i);
		if(!val[p]) {
			++ans;
			// printf("%d?\n", p);
		}
		val[p] = val[op[p]] = 1;
	}
	printf("%lld\n", ksm(2, ans));
	return 0;
}