HDU-3234 Exclusive-OR 异或带权并查集

题目描述

现在有n个数,X0,X1,…,Xn-1,你并不知道这n个数的大小,然后接下来有Q个询问,询问的格式如下

1) I p v, 告诉你 Xp = v
2) I p q v, 告诉你 Xp ^ Xq = v
3) Q k Xi, Xi+1, ..,Xi+k-1, 让你求Xi ^ Xi+1 ^..^ Xi+k-1的值

如果输入的第i个I条件和前面已知的冲突,则输出The first %d facts is conflicting.,并不输出后面的询问。
如果Q条件可以求取则输出对应的值,否则输出I don't know
题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=3234

思路

已知A^B,B^C, 则A^C唯一确定。这种关系是带权并查集的典型应用。

定义 f[x]为x的根节点, w[x]表示和父节点异或值。

如何处理询问1?我构造一个节点n,其值为0,则任一节点p其值为v等价于节点p和节点n异或值为v,从而将询问1转换为询问2,即I p v to I p n v
对于询问Q,首先求出k个数对应多少个集合以及集合的个数,如果当某一集合的节点点既不为n且其元素个数为奇数时,则无法求出结果。因为f[a] = f[b] = r, a ^ b = a ^ (c ^ c) ^ b = (a ^ c) ^ (b ^ c) = w[a] ^ w[b].
有一个WA点,在并查集merge的过程中要始终保持节点n作为根节点。否则询问Q的时候会判断错误。

AC代码

#include 
#include 
#include >
#include 
#include 
using namespace std;

const int maxN = 20005;

int n, Q;
int p, q, v, k;
char ch[5];

int f[maxN], w[maxN];
map> rootMap;
void init() {
    memset(f, -1, sizeof f);
    memset(w, 0, sizeof w);
}

int find(int x) {
    if (f[x] == -1) return x;
    int t = f[x];
    f[x] = find(t);
    w[x] ^= w[t];
    return f[x];
}

bool merge(int x, int y, int s) {
    int px = find(x);
    int py = find(y);
    if (px == py) {
        return w[x] == (w[y] ^ s);
    }
    // 要始终保持新集合的根节点为n!
    if (px == n) {
        swap(px, py);
    }
    f[px] = py;
    w[px] = w[x] ^ w[y] ^ s;
    return true;
}

int main() {
    // freopen("/Users/jeremy/CLionProjects/AcRush/input.txt", "r", stdin);
    int kase = 0;
    while (scanf("%d %d", &n, &Q) != EOF) {
        if (n + Q == 0) break;
        printf("Case %d:\n", ++kase);
        init();
        int factCnt = 0;
        bool keepSilence = false;
        for (int i = 0; i < Q; i++) {
            scanf("%s", ch);
            if (ch[0] == 'I') {
                factCnt++;
                char line[100];
                fgets(line, 100, stdin);
                int t = sscanf(line, " %d %d %d", &p, &q, &v);
                bool res = false;
                if (t == 2) {
                    res = merge(p, n, q);
                } else if (t == 3) {
                    res = merge(p, q, v);
                }
                if (keepSilence || res) continue;
                keepSilence = true;
                printf("The first %d facts are conflicting.\n", factCnt);

            } else if (ch[0] == 'Q') {
                rootMap.clear();
                scanf("%d", &k);
                for (int j = 0; j < k; j++) {
                    scanf("%d", &p);
                    int fp = find(p);
                    if (rootMap.find(fp) == rootMap.end()) {
                        rootMap[fp] = vector(0);
                    }
                    rootMap[fp].push_back(p);
                }
                if (keepSilence) continue;
                bool flag = true;
                int ans = 0;
                for (auto ite = rootMap.begin(); ite != rootMap.end(); ite++) {
                    int root = ite->first;
                    vector rootVector = ite->second;
                    if (root != n && rootVector.size() & 1) {
                        flag = false;
                        break;
                    }
                    for (auto num : rootVector) {
                        ans ^= w[num];
                    }
                }
                if (!flag) {
                    printf("I don't know.\n");
                } else {
                    printf("%d\n", ans);
                }
            }
        }
        puts("");
    }
    return 0;
}

总结

  1. sscanf的应用,通过sscanf的返回值可以知道其正确读取了多少个数。
  2. 虚拟节点的设立从而将询问1转化为询问2。

你可能感兴趣的:(题海茫茫回头是岸,数据结构,算法)