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;
}

發佈了104 篇原創文章 · 獲贊 6 · 訪問量 4萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章