UVa - 12232 - Exclusive-OR

时间限制:3.000秒

题目链接:http://uva.onlinejudge.org/index.php?option=com_onlinejudge&Itemid=8&category=24&page=show_problem&problem=3384


  有n个非负整数X[0],X[1],X[2],……,X[n -1],但是并不直接给出这n个数的值,而是逐步给出一些命令:

I p v:令X[p] = v
I p q v:令X[p] XOR X[q] = v
Q k p1 p2 … pk:求X[p1] XOR X[p2] XOR … XOR X[pk]
  如果在执行I指令时出现前后矛盾的情况,输出"The first i facts are conflicting.",i为第几条I指令,忽略之后的所有指令。

  对于每个Q指令,如果该指令之前没有出现矛盾,则输出计算结果,如果结果不确定,输出“I don't know.”


  这道题比较蛋疼,用到了异或……

  最初的想法是记录哪些值是已经确定的,执行I指令时更新信息,然后根据已经确定的值来判断,如果执行Q指令时存在不确定值的元素,则不能确定结果,否则计算即可。但是后来发现这样是错的,因为这里的运算并非普通的加减乘除,而是比较特殊的异或运算。

  异或是个比较神奇的位运算,它是两个数相同的位取0,不同的位取1。关于异或操作,有这么两个特点,一个是0 XOR a = a,另一个是a XOR a = 0。因此异或一个数偶数次等于没异或,这正是上面最初想到的算法的问题,例如已知a XOR b = 1, b XOR c = 1,那么a、b、c的值都不确定,但是可以确定a XOR c = (a XOR b) XOR (b XOR c) = 0。

  没办法从具体哪个值下手,那么我们就可以从关系下手。那么就建立类似图或者树的结构然后算的时候dfs找么?想想又太复杂,没有头绪。这个时候考虑上面的a、b、c的那个公式a XOR c = (a XOR b) XOR (b XOR c) = 0,那么我们就想到了一种“传递”异或结果的办法,建立类似树的结构,能够通过异或运算联系起来的数都放到一颗树里面,结点上面记录的是这个数和根节点的异或值。执行I操作的时候,如果两个数分属两颗树,则将两棵树合并。执行Q操作的时候,只需要判断用到的元素所属的各个树中的元素分别被用了几个。如果是偶数次,那么把这些元素同根节点的异或值异或起来,根结点等于没有参与计算,也就用不到根结点的值。如果是奇数次,那么就需要用到根结点的值了,此时把所有同根节点的值异或起来还需要再异或根节点的值才能使得根节点没有最终参与运算,如果此时不能确定根结点的值,那么也就不能确定整个式子的值。

  根据上面的想法,数据结构应当是树,并且结点附加了与根节点的异或值,并不关心它到根节点的路径。因此,这里用到了加权并查集。

  想好了算法,选好了数据结构,就可以准备编写程序了。不过我们还可以把上面的算法再简化一下。我们向这n个非负整数中再添加一个X[n] = 0,执行I指令的第一个指令时,就等于I p n v,因为0 XOR a = a,并且令X[n]始终作为根节点。这样也就不需要另外的数据结构来记录某个数的值是否确定了,只需要判断其根节点是否为X[n]即可。而当判断某棵树被用到了奇数次的时候,也只需要判断这棵树是不是以X[n]为根节点的树即可,并且最后也不需要再异或根节点的值了,因为X[n] = 0,任何值异或0都等于这个值本身。


#include 
   
   
    
    
#include 
    
    
#include 
     
     
      
      

using std::map;
using std::swap;

const int MAXN = 20000 + 10;

int pa[MAXN], d[MAXN];
int n, Q, k, num, ans_Q;
char cmd[100];
bool OK;

int findp(const int &x) {
    if(pa[x] == x) return x;
    else {
        int t = findp(pa[x]);
        d[x] ^= d[pa[x]];
        return pa[x] = t;
    }
}
inline void init() {
    for(int i = 0; i <= n; ++i) pa[i] = i, d[i] = 0;
    OK = true;
}

bool cmd_I(const int &p, const int &q, const int &v) {
    int x = findp(p), y = findp(q);
    if(x == y) {
        return (d[p] ^ d[q]) == v;
    }
    if(x == n) swap(x, y);
    pa[x] = y, d[x] = d[p] ^ d[q] ^ v;
    return true;
}

void cmd_Q() {
    scanf("%d", &k);
    map
      
      
       
        root;
    ans_Q = 0;
    for(int i = 0; i != k; ++i) {
        scanf("%d", &num);
        if(!OK) continue;

        ++root[findp(num)];
        ans_Q ^= d[num];
    }
    if(!OK) return;
    bool haveans = true;
    for(auto it = root.begin(); it != root.end(); ++it) if(((it->second) & 1) && it->first != n) {
        haveans = false;
        break;
    }
    if(haveans) printf("%d\n", ans_Q);
    else printf("I don't know.\n");
}

int main() {
    int T = 0;
    while(~scanf("%d%d", &n, &Q) && n && Q) {
        init();
        printf("Case %d:\n", ++T);
        for(int t = 0, f = 0; t != Q; ++t) {
            scanf("%s", cmd);

            if(cmd[0] == 'I') {
                if(!OK) continue;
                ++f;
                int a, b, c;
                gets(cmd);
                if(sscanf(cmd, "%d%d%d", &a, &b, &c) == 2) c = b, b = n;
                if(!cmd_I(a, b, c)) {
                    OK = false;
                    printf("The first %d facts are conflicting.\n", f);
                }
            } else {
                cmd_Q();
            }
        }
        printf("\n");
    }
}

      
      
     
     
   
   

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值