[题解]P13495 【MX-X14-T5】魔法卷轴

感觉没人讲清楚这么算为什么是对的。

思路

网格图上计数,套路地转到二分图上,\(n\) 个行作为左部点,\(m\) 列作为右部点,第 \(i\) 行和第 \(j\) 列连边权为 \(a_{i,j}\) 的边。令点权为其出边的 xor 和,问题变成钦定了一些边的边权,求给剩下边取值的方案数,使得所有点权均为 \(1\)

首先在图上去掉被钦点边权的边,更新点权。问题被弱化为了没有钦定边的情况,并被划分为了若干连通块,这里直接讨论单一连通块的解法,多个连通块的方案数可以直接乘法原理乘起来。

对于一个 \(x\) 个点 \(y\) 条边的连通块,我们可以从如何 check 连通块是否有解入手,即构造出任意一组解。考虑找到任意一棵生成树,从叶子开始自下向上确定边权,并更新点权,则树边显然被唯一确定,同时除根节点外点权都变为 \(0\)。此时若根节点点权为 \(0\) 显然有解(将所有非树边赋值为 \(0\) 便是其中一组解);容易证明若点权为 \(1\) 则一定无解,因为若有解,每条边会对两个点有贡献,那么显然有 \(\bigoplus{v_i} = 0\),同时显然也有 \(\bigoplus{v'_i} = \bigoplus{v_i}\),根节点点权为 \(1\),其余点为 \(0\) 显然矛盾(其中 \(v_i\) 表示点权)。也就是说,若 \(\bigoplus{v_i} = 1\) 则无解。

接下来需要考虑非树边能带来什么贡献。随便手玩一下容易发现这条边无论取什么值都是合法的,因为加入这条边后我们可以看作 \(v_a \leftarrow v_a \oplus c,v_b \leftarrow v_b \oplus c\)\(\bigoplus{v_i}\) 是不变的,因此显然还是有解的。于是我断言这个连通块的方案数为 \(2^{y - (x - 1)}\)

从直觉上,我们感觉仅考虑连通块的一棵生成树会算漏一些情况,但事实上是没有问题的。容易证明非树边的取值和原问题答案存在双射关系,因为当为非树边确定一组取值后,树边权值被唯一确定,此时这组取值唯一对应了原问题的一组解;同时原问题一组解总能对应一组非树边的取值。因此二者是存在双射关系的,那么上述所“断言”的方案数便是该连通块的方案数。

综上,若有解,将各连通块方案数相乘,得答案为 \(2^{nm - n - m - k + cnt}\),其中 \(cnt\) 为连通块数量。现在我们只需要计算连通块数量。

由于存在 \(\Theta(nm)\) 量级的边,直接建边不太合理。注意到对于每一行连向总会连向若干区间的列,可以利用数据结构建图。但是此时我只需要知道点之间的连通性,图具体长什么样并不重要。于是若需要从第 \(i\) 行连向第 \([l,r]\) 列,则有 \(i\) 行与 \([l,r]\) 列连通,\([l,r]\) 列之间连通,具体的我们合并 \((i,n + l)\),并差分记录列之间的连通性,最后将记录下的连通性丢到并查集上即可。

Code

#include <bits/stdc++.h>
#define re register
#define int long long
#define Mul(a,b) ((a) * (b) % mod)
#define chMul(a,b) (a = Mul(a,b))

using namespace std;

const int N = 4e5 + 10;
const int mod = 998244353;
int n,m,k,cnt;
int c[N],st[N],fp[N],val[N];
vector<int> g[N];

inline int read(){
    int r = 0,w = 1;
    char c = getchar();
    while (c < '0' || c > '9'){
        if (c == '-') w = -1;
        c = getchar();
    }
    while (c >= '0' && c <= '9'){
        r = (r << 3) + (r << 1) + (c ^ 48);
        c = getchar();
    }
    return r * w;
}

inline int qmi(int a,int b){
    int res = 1;
    while (b){
        if (b & 1) chMul(res,a);
        chMul(a,a); b >>= 1;
    } return res;
}

inline int find(int x){
    if (fp[x] == x) return fp[x];
    else return fp[x] = find(fp[x]);
}

inline void merge(int a,int b){
    int x = find(a),y = find(b);
    if (x != y) fp[x] = y;
}

signed main(){
    n = read(),m = read(),k = read();
    for (re int i = 1;i <= n + m;i++) val[fp[i] = i] = 1;
    for (re int i = 1,x,y,z;i <= k;i++){
        x = read(),y = read(),z = read();
        g[x].push_back(y);
        val[x] ^= z,val[n + y] ^= z;
    }
    for (re int i = 1;i <= n;i++){
        int lst = 1;
        g[i].push_back(m + 1);
        sort(g[i].begin(),g[i].end());
        for (int x:g[i]){
            if (lst <= x - 1){
                merge(i,n + lst);
                c[lst]++,c[x - 1]--;
            } lst = x + 1;
        }
    }
    for (re int i = 1;i <= m;i++) c[i] += c[i - 1];
    for (re int i = 1;i < m;i++){
        if (c[i]) merge(n + i,n + i + 1);
    }
    for (re int i = 1;i <= n + m;i++) st[find(i)] ^= val[i];
    for (re int i = 1;i <= n + m;i++){
        if (i == find(i)){
            cnt++;
            if (st[i]) return puts("0"),0;
        }
    } printf("%lld",qmi(2,n * m - n - m - k + cnt));
    return 0;
}
posted @ 2026-02-25 08:43  WBIKPS  阅读(2)  评论(0)    收藏  举报