HDU 3234 Exclusive-OR [并查集]

http://acm.hdu.edu.cn/showproblem.php?pid=3234
#Description
给你N个数,X0…X(N-1)
执行Q个查询
三种格式
I p v
Xp= v
I p q v
Xp XOR Xq = v
Q k p1 p2 … pk
回答Xp1 XOR Xp2 XOR … XOR Xpk 是多少?
对于每个Q
如果之前提供的信息不能算出来,输出
“I don’t know.”
如果第i个 I 与之前的 I 有矛盾,输出
“The first i facts are conflicting.”
然后后面的内容全部不管
否则就输出答案
#Algorithm
##XOR的性质

  1. X XOR 0 = X 任何数XOR自己为他们本身
  2. X XOR X = 1 任何数XOR自己 = 1
  3. A XOR B XOR C = A XOR C XOR B XOR满足交换律

带权并查集
权值v[i] = a[i] XOR a[id[i]] // id[i]就是代码中的par[i]
然后还需要设置超级根 a[N] = 0
由XOR性质1可得
这样 I p v 就是 I p n v
二者都执行 unite(p, q, w) //代码中的 void unite(int x, int y, int w)
如果p q的root都一样
由XOR性质2,3可得
p xor q = p xor root xor q xor root = v[x] XOR v[y]
这样就可以判断是否矛盾
我的代码里就没有使用加权的并查集,因为显然超级根N最适合做根
这样默认j做i的父亲//代码中卫fy 做 fx的父亲
新的v[i] = x[i] xor x[j]
因为 v[p] = x[p] xor x[i] v[q] = x[q] xor x[j] w = x[p] xor x[q]
所以 v[i] = v[p] xor w xor v[q]
find时
v[p] 原本是 x[p] xor x[id[p]]
但由于路径压缩
新的v[p] = x[root] xor x[p]
v[id[p]] = x[root] xor x[id[p]]
所以
v[p]只需要 ^= v[id[p]]就好了
Q的时候
用个ans把全部v[i] XOR
因为v[i] = x[i] XOR x[id[i]]
如果id[i]出现了偶数次,就消掉了
当然了,n可以不管多少次
因为如果 id[i] = n 那么 v[i] = x[i]
这样出现奇数次的id并且不是n,那么就不知道了
#Code

//约定 a[i] 表示 a[i]的值
#include <iostream>
#include <cstdio>
#include <cstring>
#include <map>

using namespace std;
const int MAXN = 20000 + 9;
int par[MAXN];
int v[MAXN]; //当前值是a[i] v[i] = a[i] xor a[par[i]]
bool bug, know;
struct UnionFind {
    int n;
    UnionFind(){};
    void init(int nn) {
      n = nn;
      //memset(v, 0, sizeof(v));
      for (int i = 0; i <= n; i++) {
        par[i] = i;
        v[i] = 0;
      }
    }
    int find(int x) {
      if (par[x] != x) {
        int fx = find(par[x]);
        v[x] ^= v[par[x]];
        /**
         * v[x] = a[x] xor a[par[x]]
         * v[par[x]] = a[par[x]] xor a[par[par[x]]]
         */
        par[x] = fx;
      }
      return par[x];
    }
    void unite(int x, int y, int w) { //a[x] xor a[y] = w
      int fx = find(x);
      int fy = find(y);
      if (fx == fy) {
        if ((v[x]^v[y]) != w) bug = true;
        /**
         * v[x] = a[x] xor a[fx]
         * v[y] = a[y] xor a[fy]
         * fx == fy a[fx] = f[fy]
         * v[x] xor v[y] = a[x] xor a[y] = w
         */
        return;
      }
      if (fx == n) swap(fx, fy);
      par[fx] = fy;
      v[fx] = v[x] ^ w ^ v[y];
      /**
       * 此处没用启发式合并
       * v[x] = a[x] xor a[fx]
       * v[y] = a[y] xor a[fy]
       * 现在par[x] 变成 fy
       * v[fx] = a[fx] xor a[par[x]] = a[fx] xor a[fy]
       * w = a[x] xor a[y]
       * v[fx] = v[x] xor w xor v[y]
       */
    }
    bool same(int x, int y) {
      return find(x) == find(y);
    }
    void print() {
      for (int i = 0; i < n; i++) {
        printf("%d ", par[i]);
      }
      cout << endl;
    }
    int total() {
      int s = 0;
      for (int i = 0; i < n; i++) {
        if (par[i] == i) s++;
      }
      return s;
    }
};
int n, q;
char s[1000]; //读入字符串
int Itotal;
UnionFind unionFind;
void solveIpv(int p, int v) {
 // cout << "p=" << p << "v=" << v << endl;
  unionFind.unite(p, n, v);
}
void solveIpqv(int p, int q, int v) {
  //cout << "p=" << p << "q=" << q << "v=" << v << endl;
  unionFind.unite(p, q, v);
}
void solveI() {
  Itotal++;
  gets(s);
  if (bug) return;
  int a, b, c;
  if (sscanf(s, "%d%d%d",&a, &b, &c) == 2) {
    solveIpv(a, b);
  }else {
    solveIpqv(a, b, c);
  }
  if (bug) {
    printf("The first %d facts are conflicting.\n", Itotal);
  }
}
void solveQ() {
  //unionFind.print();
  know = true; //是否输出i don't know
  int k;
  scanf("%d", &k);
  //cout << "k=" << k << endl;
  int ans = 0;
  map<int, int> map1; //用来存父亲出现的次数
  for (int i = 0; i < k; i++) {
    int x;
    scanf("%d", &x);
    //cout << "x=" << x << endl;
    int fx = unionFind.find(x);
 //   cout << "x = " << x << "fx = " << fx << endl;
    ans ^= v[x];
    map1[fx]++;
  }
  if (bug) return;
  for (map<int, int>::iterator it = map1.begin(); it != map1.end(); it++) {
    if ((it -> second) % 2) { //相同父亲节点是奇数个 xor 消不了
      if (it->first != n) {
      //  cout << "*" << it->first << endl;
        know = false;
        break;
      }
    }
  }
  if (!know) {
    puts("I don't know.");
  } else {
    printf("%d\n", ans);
  }
}
void solve() {
  Itotal = 0; //ca2
  bug = false;
  unionFind.init(n);
  for (int i = 0; i < q; i++) {
   //unionFind.print();
    char s[20];
    scanf("%s", s);
    //   cout << "ch=" << ch << endl;
    if (s[0] == 'I') {
      solveI();
    }else {
      solveQ();
    }
  }
}
int main() {
  //freopen("in.txt", "r", stdin);
  for (int i = 1;;i++) {
    scanf("%d%d\n", &n, &q);
    if (n == 0) break;
    printf("Case %d:\n", i);
    solve();
    printf("\n");
  }
}

#第一次写的时候
我的内心是崩溃的
Debug了一个小时,结果,少了一个括号

((v[x]^v[y]) != w)
(v[x]^v[y] != w)

这两个表达式的结果是不一样了 = =
位运算的优先级低我是知道的,没有想到这么低。还能过Sample,这就让人很崩溃了。

以后凡是有位运算的,一律打括号!
这题很有意义,要好好写题解,不过先游个泳,冷静一下。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值