LA4487 Exclusive-OR (加权并查集)

题意: 有N个数X[0] ~ X[N], 事先并不知道他们的值,有三种操作(^为异或):

I P V :告诉你X[P] = V
I P Q V:告诉你X[P] ^ X[Q] = V
Q K P1..PK:询问X[P1]^X[P2]^...X[PK]的值

如果信息矛盾 或者 不能知道查询的结果输出一些提示信息。


思路:对于第一种操作, X[P] = V 可以看做第二种操作的 X[P] ^ 0 = V, 新增一个节点n, 他的值为0。

对于查询, 我们知道, 如果有a ^ b = x1, b ^ c = x2, 可以得出 a ^ c = x1 ^ x2, 那么如果Xa ^ Xb = v为a, b连接一条边,权值为v,则a^b能求出结果的条件是a, b有相同的根节点,这里可以使用并查集, 注意在合并的时候需要进行路径压缩, 即每一节点有一个与根节点异或的值。那么在查询的时候, 先把不同集合里面的节点分开, 相同集合里面的节点放一起, 在同一个集合里面, 如果根节点是n, 那么一定可以得出所有节点的异或值, 如果不为n, 那么只要出现节点个数为奇数的集合就不能得出结果, 否则将每个节点的值异或起来即可。


#include<cstdio>
#include<cstring>
#include<cmath>
#include<vector>
#include<iostream>
#include<algorithm>
const int maxn = 2e4 + 10;
using namespace std;

int n, Q;
char cmd[maxn];
int pa[maxn], dis[maxn];

int num, r[50], fac;
int read() {
    int len = strlen(cmd);
    num = 0;
    for(int i = 0; i < len; i++) {
        if(cmd[i] < '0' || cmd[i] > '9') continue;
        int j = i, sum = 0;
        while(j < len && cmd[j] >= '0' && cmd[j] <= '9') {
            sum = sum * 10 + cmd[j] - '0';
            j++;
        }
        r[num++] = sum;
        i = j;
    }
    char ch = cmd[0];
    if(ch == 'Q') return 3;
    fac++;
    if(num == 2) return 1;
    return 2;
}

int findset(int x) {
    if(x == pa[x]) return x;
    int px = findset(pa[x]);
    dis[x] ^= dis[pa[x]];
    return pa[x] = px;
}

bool can_unit(int x, int y, int val) {
    int nx = findset(x), ny = findset(y);
    if(nx == ny && (dis[x] ^ dis[y]) != val) return false;
    if(nx == ny) return true;
    if(nx == n + 1) {
        pa[ny] = nx;
        dis[ny] = dis[x] ^ dis[y] ^ val;
    } else {
        pa[nx] = ny;
        dis[nx] = dis[x] ^ dis[y] ^ val;
    }
    return true;
}

vector<int> G[50];
int fa[50];

int main() {
    int kase = 1;
    while(scanf("%d %d", &n, &Q) && n) {
        fac = 0;
        for(int i = 0; i <= n + 2; i++) {
            pa[i] = i; dis[i] = 0;
        }
        gets(cmd);
        bool ans = true;
        printf("Case %d:\n", kase++);
        while(Q--) {
            gets(cmd);
            if(!ans) continue;
            int op = read();
            if(op == 1) {
                int p = r[0], v = r[1];
                if(!can_unit(p, n + 1, v)) { ans = false; printf("The first %d facts are conflicting.\n", fac); }
            } else if(op == 2) {
                int p = r[0], q = r[1], v = r[2];
                if(!can_unit(p, q, v)) { ans = false; printf("The first %d facts are conflicting.\n", fac); }
            } else {
                
                int x = 0;
                for(int i = 0; i < 20; i++) G[i].clear();
                for(int i = 1; i < num; i++) {
                    int id = r[i], nx = findset(id);
                    bool have = false;
                    for(int j = 0; j < x; j++) {
                        if(fa[j] != nx) continue;
                        have = true;
                        G[j].push_back(id);
                        break;
                    }
                    if(have) continue;
                    G[x].push_back(id); fa[x] = nx;
                    x++;
                }
                bool solve = true;
                int res = 0;
                for(int i = 0; i < x; i++) {
                    if(fa[i] != n + 1 && G[i].size() % 2 == 1) { solve = false; break; }
                    for(int j = 0; j < G[i].size(); j++) {
                        int k = G[i][j];
                        res ^= dis[k];
                    }
                }
                if(!solve) printf("I don't know.\n");
                else printf("%d\n", res);
            }
        }
        printf("\n");
    }
    return 0;
}


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值