您的位置:首页 > 其它

UVALIVE 4487 Exclusive-OR(加权并查集)

2015-09-21 23:43 411 查看
题目链接:https://icpcarchive.ecs.baylor.edu/index.php?option=com_onlinejudge&Itemid=8&page=show_problem&problem=2488

题意:已知有n个数,但并不知道大小。有如下3种操作:

I a w:下标为u的数值为w。

I a b w:下标为u的数和下标为v的数的异或值为w。

Q k z1…zk:求下标为z1到zk的数的异或值。

思路:并查集,但并不是简单的并查集,在并查集所构成的树上的每条边都加了一个权值。我的做法如下:

f[i]:存储i的根结点标号。

v[i]:存储i与f[i]的异或值。

p[i]:存储i的值。

int find(int x):找x的根并做压缩的同时处理好每个节点与根的异或值

void judge(int c, int r):若已知根r的值或已知孩子c的值,则求另外一个节点的值。

bool uni(int r, int &ans) :求每一个根结点为r的联通块的异或值,可求返回true,否则false。

首先对I操作来看:

若为操作1,则令 p[a] = w。

然后需要判断是否与之前的事实矛盾:用 find() 函数求 a 的根结点 r .

因为已知 p[a] 的值和 v[a] 的值,即理论上由异或的性质就可以求出 p[r] 的值。但若 p[r] 已经赋值,则需判断 w 与 p[r] 是否相同。

若为操作2,则用 find() 函数先求 a 的根 r1 和 b 的根 r2 。

若 r1 和 r2 不同根,则并在一起 f[r1] = r2,同时求出 v[r1] = w ^ v[a] ^ v[b],调用 judge() 函数。

若相同根,则只需判断是否合法,即 w 是否等于 v[a] ^ v[b]。

若为操作3,即询问。

先对询问中的节点构成的联通块进行合并(若2个不同联通块根的值都已知,则可以合并)。

然后对每个联通块求异或值(可以先求每个联通块的根)。

对于一个联通块,若节点数为奇数并且根的值未知,则无法求该联通块的异或值,否则可以求。

注意点:

对于一个联通块,若知道其中一个节点的值,则可以知道联通块中每个节点的值。

合并联通块的时候,需调用judge()函数,因为若一个联通块内的所有节点的值均知道,则合并后可知道另一个联通块内的所有节点的值。

输出don’t 的时候,单引号是英式的!!

代码:

(代码略长,但效率应该不低= =)

#include <stdio.h>
#include <iostream>
#include <math.h>
#include <algorithm>
#include <string.h>

using namespace std;

#define LSON l, m, rt << 1
#define RSON m + 1, r, rt << 1 | 1

const int N = 2e4 + 10;
const int M = 1e3 + 10;
const int INF = (1 << 20) + 10;

int num, n, err, ifacts;
int a[M];
int f
;
int v
;
int p
;
char str[M];

void init(char *s) {
    int len = strlen(s);
    s[len] = ' ';
    s[++len] = '\0';
    num = 0;
    int t = 0;
    for (int i = 2; i < len; i++) {
        if (s[i] != ' ') {
            t = t * 10 + s[i] - '0';
        }
        else {
            a[num++] = t;
            t = 0;
        }
    }
}

void judge(int c, int r) {//若已知根的值或已知孩子值,则求另外一个节点的值
    if (p[c] != INF) {//孩子值已知
        if (p[r] == INF)//根值未知
            p[r] = (v[c] ^ p[c]);
        else if (p[r] != (v[c] ^ p[c]))//根值已知,判断是否是否合法
            err = ifacts;
    }
    else if (p[r] != INF) {//孩子值未知,根值已知
        p[c] = (p[r] ^ v[c]);
    }
}

int find(int x) {//找根的同时,计算每个节点和根的异或值,存入v数组
    if (f[x] != x) {
        int r = f[x]; 
        f[x] = find(f[x]);
        v[x] = v[r] ^ v[x];
        judge(x, f[x]);
    }
    return f[x];
}

bool uni(int r, int &ans) {//求每一个联通块的异或值,可求返回true,否则false
    int rv = p[r];
    int tmp = 0, cnt = 0;
    for (int i = 1; i < num; i++) {
        if (f[a[i]] == r) {
            tmp ^= v[a[i]];
            cnt++;
        }
    }
    if (cnt % 2 == 0) {
        ans ^= tmp;
        return true;
    }
    else {//奇数个节点,需判断根的值是否知道
        if (rv == INF)
            return false;
        else 
            ans ^= (tmp ^ rv);
        return true;
    }
}

int main() {
    int q, i_case = 1;
    while (scanf("%d%d", &n, &q) != EOF && n && q) {
        ifacts = 0;
        err = -1;
        for (int i = 0; i < N; i++) {
            f[i] = i;
            v[i] = 0;
            p[i] = INF;
        }
        getchar();
        printf("Case %d:\n", i_case++);
        for (int i = 0; i < q; i++) {
            gets(str);
            if (err != -1)
                continue;
            init(str);
            if (str[0] == 'I') {
                ifacts++;
                if (num == 3) {//给出2个节点的异或值
                    int r1 = find(a[0]);
                    int r2 = find(a[1]);
                    if (r1 != r2) {//若不同根
                        f[r1] = r2;
                        v[r1] = (a[2] ^ v[a[0]] ^ v[a[1]]);
                        judge(r1, r2);
                    }
                    else if (a[2] != (v[a[0]] ^ v[a[1]])) {
                        err = ifacts;
                        printf("The first %d facts are conflicting.\n", ifacts);
                    }
                }
                else {//给出1个节点的值
                    int r = find(a[0]);
                    if (p[a[0]] != INF && p[a[0]] != a[1]) {
                        err = ifacts;
                        printf("The first %d facts are conflicting.\n", ifacts);
                    }
                    else
                        p[a[0]] = a[1];
                    judge(a[0], r);
                }
            }
            else {
                if (num >= 2) {
                    int r = find(a[1]);
                    for (int i = 1; i < num; i++) {//合并可以合并的联通块,即若2个联通块的根的值都已知,则可以合并
                        int tr = find(a[i]);
                        if (tr != r && p!= INF && p[r] != INF) {
                            f= r;
                            v= (p^ p[r]);
                            judge(tr, r);
                        }
                    }
                    int root[20], rn = 0;
                    root[rn++] = find(a[1]);
                    for (int i = 2; i < num; i++) {//求出每个联通块的根
                        int r = find(a[i]);
                        bool hav = false;
                        for (int j = 0; j < rn; j++)
                            if (root[j] == r) {
                                hav = true;
                                break;
                            }
                        if (!hav)
                            root[rn++] = r;
                    }
                    bool dont = false;
                    int ans = 0;
                    for (int i = 0; i < rn; i++) {
                        if (!uni(root[i], ans)) {//对于每个联通块求异或值,若某个联通块无法求值,则don't know
                            dont = true;
                            break;
                        }
                    }
                    if (dont)
                        printf("I don't know.\n");
                    else
                        printf("%d\n", ans);
                }
            }
        }
        printf("\n");
    }
    return 0;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: