Loading

CF2022E 题解 | 数学、并查集

传送门

标签:数学、并查集

题意

给你一个 \(n\)\(m\) 列的矩阵 \(a\),满足限定条件:
对于 \(\forall i_1, i_2, j_1, j_2, a_{i_1, j_1} \oplus a_{i_2, j_1} \oplus a_{i_1, j_2} \oplus a_{i_2, j_2} = 0\)
换句话说,就是所有的子矩阵都满足四个角上的值异或和为 \(0\)

同时,给出 \(k\) 个约束,形如 \(a_{r, c} = v\)
给出 \(q\) 个持久化修改,形如 \(a_{r, c} = v\)

思路

\[\because a_{i_1, j_1} \oplus a_{i_2, j_1} \oplus a_{i_1, j_2} \oplus a_{i_2, j_2} = 0 \]

\[\therefore a_{i_1, j_1} \oplus a_{i_1, j_2} = a_{i_2, j_1} \oplus a_{i_2, j_2} \]

即,给定 \(i_1, i_2\),这两行中同一列的两个数异或和是定值,也就是不受到 \(j\) 的影响。

同时结合异或的性质,我们已经可以猜想:构造一个数列 \(Y_{1,...,m}\)\(a_{i,j}\)\(Y_{j}\) 异或另一些值得到的。
同理,我们将行和列换过来,可以得到类似的直觉,于是大胆猜想:\(a_{i,j}=X_{i} \oplus Y_{j}\)

经过验证,这是满足题目限定的一种构造。

证明:\(a_{i,j}=X_{i} \oplus Y_{j}\)

观察这个式子,肯定要将一个坐标和行、列结合。
又是异或运算,容易想到 \(a'_{i,j} \leftarrow a_{i,j} \oplus a_{i,1} \oplus a_{1,j}\)

然后我们发现,这样之后得到的新的 \(a'\) 依然是满足条件的矩阵。

证明:

每个子矩阵的表达式都异或上了 \(a_{i_1, 1}, a_{i_2, 1}, a_{1, j_1}, a_{1, j_2}\) 且都异或两遍。

那么,考虑一个左上角的矩阵(即 \(i_1 = j_1 = 1\)):

\[\because a'_{1, 1} = a'_{1, j_2} = a'{i_2, 1} = a_{1, 1} \]

\[\therefore a'_{i_2, j_2} = a_{1, 1}, \therefore a_{i_2, j_2} = a_{1, 1} \oplus a_{i_2, 1} \oplus a_{1, j_2} \]

于是已经基本证明完了,给出一个构造:

构造 \(X_{i} = a_{i, 1} \oplus a_{1, 1}, Y_{j} = a_{1, j}\) 即可。

这样的话,就可以应用方格图常用套路,将横纵坐标连边。含义为:若一个点的值(对应 \(X\)\(Y\))确定,则另一个点确定。

那么对于限制 \(a_{r, c} = v\),将边权设置为 \(v\) 即可。

判定无解的条件即为:存在 \(u, v\),存在两条从 \(u\)\(v\) 的路径,且路径上的异或和不同。
转换为存在一个异或和非 \(0\) 的环。

若有解,则每个联通块的可能性即为 \(V = 2 ^ {30}\),那么总共的序列 \(X\)\(Y\)\(2 ^ {{30} ^ {cnt}}\) 种,其中 \(cnt\) 为联通块个数。

而每 \(2 ^ {30}\)\(X\)\(Y\) 的取值会对应相同的一个矩阵 \(a\)。(将 \(X\)\(Y\) 同时异或一个 \(p, p \in [0, 2 ^ {30} - 1]\) 不会影响 \(a\)

于是答案为 \(2 ^ {{30} ^ {cnt - 1}}\)

由于修改是持久的,拓展域带权并查集维护即可。

代码

link

const int N = 2e5 + 5;
const int mod = 1e9 + 7;
int n, m, k, q;
int cnt = 0;
bool flg = 1;
ll qpow(ll a, int b){
	ll res = 1;
	while(b){
		if(b & 1) res = res * a % mod;
		a = a * a % mod;
		b >>= 1;
	}
	return res;
}
struct dsu{
	int fa[N], val[N], siz[N];
	void init(int n){
		rep(i, 1, n) fa[i] = i, val[i] = 0;
	}
	int find(int x){
		if(fa[x] == x) return x;
		int top = find(fa[x]);
		val[x] ^= val[fa[x]];
		fa[x] = top;
		return top;
	}
	bool merge(int u, int v, int w){
		int fu = find(u), fv = find(v);
		if(fu == fv){
			if((val[u] ^ val[v] ^ w) != 0){
				return 0;
			}
			else return 1;
		}
		if(siz[fu] > siz[fv]) swap(u, v), swap(fu, fv);
		fa[fu] = fv;
		val[fu] = val[u] ^ w ^ val[v];
		cnt--;
		return 1;
	}
} dsu;
ll calc(){
	if(!flg) return 0ll;
	return qpow((1ll << 30), cnt - 1);
}
void solve_test_case(){
	n = read(), m = read(), k = read(), q = read();
	flg = 1, cnt = n + m, dsu.init(n + m);
	rep(i, 1, k){
		int r = read(), c = read(), v = read();
		if(!dsu.merge(r, c + n, v)) flg = 0;
	}
	write(calc());
	rep(i, 1, q){
		int r = read(), c = read(), v = read();
		if(!dsu.merge(r, c + n, v)) flg = 0;
		write(calc());
	}
}
signed main(){
    #ifdef FILE_NAME
        freopen((string(MACRO_STR(FILE_NAME)) + ".in").c_str(), "r", stdin);
        freopen((string(MACRO_STR(FILE_NAME)) + ".out").c_str(), "w", stdout);
    #endif
    int Test_case_num = read();
    while(Test_case_num--) solve_test_case();
    return 0;
}
posted @ 2025-09-10 15:17  lajishift  阅读(5)  评论(0)    收藏  举报