uvalive4487 带权并查集

两种操作,I p q v表示p^q = v,如果与之前有冲突, 则输出“The first i facts are conflicting.”其中i为之前所有的I操作的次数(算上当前冲突这次)。Q k p1p2..pk表示求p1^p2...^pk的值,输出值或“I don't know.”

首先,I操作后面跟的参数个数不确定所以用if(sscanf(s, "%d%d%d", &p, &q, &v) == 2)来判断参数的个数。

再有,用d[i]表示i与其父节点的异或值,输入p q的时候就利用并查集来更新d。如果只输入p时怎么办呢?不妨建立一个虚节点n,如果输入的是一个节点的值,就让它的父节点指向n,这样d[p]就是p与n的异或了(n初始为0)。输入的时候判断是否有冲突,如果没有的话,就合并,n总是要做为父节点的(如果有的话)。

然后是询问部分。假如询问发生在一个集合中,包含k个点,如果k为偶数,那么直接把k个d[]异或起来就是答案;如果k是奇数,直接异或会多一个根的值。理由:经过find(i)之后,d[i]表示i与该集合根节点的异或值,那么偶数次异或同一个值就相当于什么都没有发生;奇数次呢就会多异或了一个根节点的值,而我们一定不知道根结点的值,除非根节点为n,因为所有已知值的节点都是n的子结点啊。理解这个,就可以判断并求值了。

最后把所有d[pi]异或在一起就是答案,根节点每出现过一次就标记一下(我习惯用vis数组来标记),判断一下是否有出现过奇数次的,且是不是n。记得把数组清零,我那样写是因为k不超过15,循环也不费时间,但把整个vis数组都循环赋值一遍的话,还是要消耗一点时间的。


关键理解:一个值被异或偶数次,就等于没异或。那么每个节点保存的是它与父节点的异或值,当你把每个节点的值异或起来时,如果它们中有父节点被异或了奇数次,那么这时你就多异或了一个不要异或的值。如果这个父节点是虚结点n的话,那没关系因为n的d值是0,异或它等于本身。但是如果不是n那么就判断不出。因为这个多异或的父节点值不知道它的d值是多少。


#include<algorithm>
#include<iostream>
#include<cstring>
#include<cstdio>
#include<vector>
#include<string>
#include<queue>
#include<cmath>
///LOOP
#define REP(i, n) for(int i = 0; i < n; i++)
#define FF(i, a, b) for(int i = a; i < b; i++)
#define FFF(i, a, b) for(int i = a; i <= b; i++)
#define FD(i, a, b) for(int i = a - 1; i >= b; i--)
#define FDD(i, a, b) for(int i = a; i >= b; i--)
///INPUT
#define RI(n) scanf("%d", &n)
#define RII(n, m) scanf("%d%d", &n, &m)
#define RIII(n, m, k) scanf("%d%d%d", &n, &m, &k)
#define RIV(n, m, k, p) scanf("%d%d%d%d", &n, &m, &k, &p)
#define RV(n, m, k, p, q) scanf("%d%d%d%d%d", &n, &m, &k, &p, &q)
#define RFI(n) scanf("%lf", &n)
#define RFII(n, m) scanf("%lf%lf", &n, &m)
#define RFIII(n, m, k) scanf("%lf%lf%lf", &n, &m, &k)
#define RFIV(n, m, k, p) scanf("%lf%lf%lf%lf", &n, &m, &k, &p)
#define RS(s) scanf("%s", s)
///OUTPUT
#define PN printf("\n")
#define PI(n) printf("%d\n", n)
#define PIS(n) printf("%d ", n)
#define PS(s) printf("%s\n", s)
#define PSS(s) printf("%s ", n)
///OTHER
#define PB(x) push_back(x)
#define CLR(a, b) memset(a, b, sizeof(a))
#define CPY(a, b) memcpy(a, b, sizeof(b))
#define display(A, n, m) {REP(i, n){REP(j, m)PIS(A[i][j]);PN;}}

using namespace std;
typedef long long LL;
typedef pair<int, int> P;
const int MOD = 100000000;
const int INFI = 1e9 * 2;
const LL LINFI = 1e17;
const double eps = 1e-6;
const double pi = acos(-1.0);
const int N = 22222;
const int M = 22;
const int move[8][2] = {0, 1, 0, -1, 1, 0, -1, 0, 1, 1, 1, -1, -1, 1, -1, -1};

int n;
bool vis[N];
char op[2], s[M];
int f[N], d[N], a[M];

int find(int x)
{
    if(x == f[x])return x;
    int tmp = f[x];
    f[x] = find(f[x]);
    d[x] ^= d[tmp];
    return f[x];
}

bool add(int p, int q, int v)
{
    int x = find(p);
    int y = find(q);
    if(x == y)return v == (d[p] ^ d[q]);
    if(x == n)swap(x, y);
    f[x] = y;
    d[x] = d[p] ^ d[q] ^ v;
    return 1;
}

int main()
{
    //freopen("input.txt", "r", stdin);
    //freopen("output.txt", "w", stdout);

    int m, p, q, v, k, fact, go, cas = 1;
    while(RII(n, m), n + m)
    {
        REP(i, n + 1)f[i] = i;
        CLR(vis, 0);
        CLR(d, 0);
        fact = 0;
        go = 1;
        printf("Case %d:\n", cas++);
        while(m--)
        {
            RS(op);
            if(!go)
            {
                gets(s);
                continue;
            }
            if(op[0] == 'I')
            {
                fact++;
                gets(s);
                if(sscanf(s, "%d%d%d", &p, &q, &v) == 2)v = q, q = n;
                if(!add(p, q, v))
                {
                    printf("The first %d facts are conflicting.\n", fact);
                    go = 0;
                }
            }
            else
            {
                RI(k);
                v = 0;
                REP(i, k)
                {
                    RI(a[i]);
                    p = find(a[i]);
                    v ^= d[a[i]];
                    a[i] = p;
                    vis[p] = !vis[p];
                }
                q = 1;
                REP(i, k)if(vis[a[i]])
                {
                    if(a[i] != n)q = 0;
                    vis[a[i]] = 0;
                }
                if(q)PI(v);
                else puts("I don't know.");
            }
        }
        puts("");
    }
    return 0;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值