题目链接: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[N];
int v[N];
int p[N];
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[tr] != INF && p[r] != INF) {
f[tr] = r;
v[tr] = (p[tr] ^ 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;
}