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

题目链接:https://icpcarchive.ecs.baylor.edu/index.php?option=com_onlinejudge&Itemid=8&page=show_problem&problem=2488

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

  1. I a w:下标为u的数值为w。
  2. I a b w:下标为u的数和下标为v的数的异或值为w。
  3. 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. 若为操作1,则令 p[a] = w。
    然后需要判断是否与之前的事实矛盾:用 find() 函数求 a 的根结点 r .
    因为已知 p[a] 的值和 v[a] 的值,即理论上由异或的性质就可以求出 p[r] 的值。但若 p[r] 已经赋值,则需判断 w 与 p[r] 是否相同。

  2. 若为操作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. 若为操作3,即询问。
    先对询问中的节点构成的联通块进行合并(若2个不同联通块根的值都已知,则可以合并)。
    然后对每个联通块求异或值(可以先求每个联通块的根)。
    对于一个联通块,若节点数为奇数并且根的值未知,则无法求该联通块的异或值,否则可以求。

注意点:

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

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

  3. 输出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;
}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值