杭電3234——帶權並差集(異或運算)

杭電3234——帶權並差集(異或運算)

寫在題前:
  • 拿到題的時候,woc,這是個什麼鬼題哦!
  • 剛開始不知道異或的一些運算,所以沒怎麼有思路,後來找了異或公式,還是沒怎麼有思路,哈哈哈。
  • 有用的異或公式:a ^ b ^ b ^ c ^ b ^ c ^ b ^ c = a ^ c 。(就是可以消去兩個相同的元素,有點消消樂的感覺)
  • 可以搜一下異或運算的應用,比如說:判斷重複元素等等。還是挺有意思的。
解題思路:

帶權並差集:權值就是兩個節點的異或值。

  1. 判斷需要進行的操作
  2. I p v :就把 p 和虛擬節點n建立聯繫(如果這個集合裏面有n,說明這個集合能計算出具體的異或值)。
  3. I p q v :把 p 和 q 建立聯繫,權值爲v
  4. Q 操作:進行查詢。

具體見代碼解析:

寫在題後:
  • 這題有坑,這題有坑,這題有坑!!!重要事情說三遍。輸入多組樣例,每組樣例之間用空行隔開,最後一個樣例也有空行!,但是呢,題中給的樣例輸出就沒有空行,你說氣不氣。
  • 這個題難度還行,就是感覺這個操作的過程太複雜了,好好捋一捋,轉過這個彎之後,還算挺好做的!
ac代碼
# include <iostream>
# include <algorithm>
# include <cstdio>
# include <cstdlib>
# include <string>
# include <cstring>
# include <vector>
# include <map>
using namespace std;
typedef long long ll;

const int maxn = 20010;
int id[maxn];//記錄父親節點
int weight[maxn];//記錄和父親節點的異或值(權值)
int num[maxn];//記錄需要查詢的節點集合
int m, n;

int fin(int x) {//尋找x節點最上面的父親節點
	if (x != id[x]) {
		int t = id[x];
		id[x] = fin(id[x]);
		weight[x] ^= weight[t];
		//路徑壓縮的同時,更新權值,
	}
	return id[x];
}

bool meg(int u, int v, int w) {// u 和 v 建立連接,權值爲 w,返回能否建立連接
	int a = fin(u);//u最上面的父親節點
	int b = fin(v);//v最上面的父親節點
	if (a == b) {//如果 u、v 已經有關係
		if ((weight[u] ^ weight[v]) != w) {
		//判斷輸入的數據是否有邏輯錯誤
			return false;//有錯誤,返回false
		}
		else {
			return true;//沒有錯誤,返回true
		}
	}
	if (a == n) {
	//這個if語句非常重要,非常重要,非常重要。
	//如果a是虛擬節點的時候,a 和 b 相互交換。
	//目的:保證a永遠不是不是虛擬節點,保證虛擬節點是最上面的節點。
		a = b;
		b = n;
	}
	id[a] = b;// a、b 建立連接
	weight[a] = weight[u] ^ weight[v] ^ w;//更新權值,公式解釋見圖1
	return true;
}

int main() {
	int Ti = 1;
	while (cin >> n >> m) {
		if (n == 0 && m == 0) {
			break;
		}
		cout << "Case " << Ti++ << ":" << endl;
		for (int i = 0; i < n + 10; i++) {
			id[i] = i;
		}
		memset(weight, 0, sizeof(weight));
		//初始化

		bool Return = false;//記錄是否有錯誤的輸入
		int cnt = 0;//記錄 i 操作的個數
		while (m--) {
			string str;
			cin >> str;
			if (str[0] == 'I') {
				cnt++;
				string anw;
				getline(cin, anw);
				anw.push_back(' ');
				int u, v, w;
				int k = 0;
				vector<int>kt;
				for (int i = 1; i < anw.size(); i++) {
					if (anw[i] == ' ') {
						kt.push_back(k);
						k = 0;
					}
					else {
						k = k * 10 + anw[i] - '0';
					}
				}
				if (kt.size() == 2) {
					u = kt[0];
					v = n;
					w = kt[1];
				}
				else if (kt.size() == 3) {
					u = kt[0];
					v = kt[1];
					w = kt[2];
				}
				if (Return) {
					continue;
				}
				//以上是將 i 操作的數字放進 u、v、w 裏面的操作(還可以用c裏面的sscanf來操作,可能會簡單一點)
				//如果只有 i 後面只有兩個數字,v的值就是n。
				
				if (!meg(u, v, w)) {//如果不能建立連接,以後的操作都不考慮
					cout << "The first " << cnt << " facts are conflicting." << endl;
					Return = true;
				}
			}
			else if(str[0]=='Q'){
				int K;
				cin >> K;
				for (int i = 0; i < K; i++) {
					cin >> num[i];//把需要查詢的節點放進 num 數組裏
				}
				if (Return) {
					continue;
				}
				
				int ans = 0;//記錄查詢節點之間的異或值
				bool vis[maxn];//記錄查詢的節點是否被訪問
				memset(vis, false, sizeof(vis));
				
				for (int i = 0; i < K; i++) {
					if (vis[i]) {//如果節點已經被訪問,就無需訪問
						continue;
					}
					int len = 0;
					//記錄查詢的的節點的個數
					//如果是奇數,就不可能算出答案
					//如果是偶數,就可以算出答案
					int froot = fin(num[i]);
					for (int j = i; j < K; j++) {
						if (vis[j]==false && fin(num[j]) == froot) {
						//如果 j 節點沒有被訪問過,i 和 j 屬於同一個集合
						//就將 j 節點放進 ans 裏。
							len++;
							vis[j] = true;
							ans ^= weight[num[j]];
						}
					}
					if (froot != n && len % 2 == 1) {
					//如果這個集合裏面沒有虛擬節點 n 而且集合元素個數是奇數
					//那麼一定不能算出異或值。返回-1.
						ans = -1;
						break;
					}
					//關於能計算出來異或值的情況有兩種,只要滿足一種就能計算出集合元素之間的異或值
					//1、如果集合裏面有虛擬節點 n ,說明集合裏面有一個節點的值是知道的,通過他們之間的異或關係,能知道所有節點的值,集合元素之間的異或值也能計算出來。見圖2
					//2、如果集合裏面節點的個數是偶數個,就可以通過異或的性質來計算出集合的異或值。因爲ans記錄的是集合每個元素和其父親節點之間的異或值,偶數個的話,能把父親節點消了了,見圖3、4。
				}
				if (ans == -1) {
					cout << "I don't know."<<endl;
				}
				else {
					cout << ans << endl;
				}
			}
		}
		cout << endl;//注意每個樣例後面都有一個空行。
	}
	return 0;
}
代碼中圖片

照片尺寸有點大,見諒!!!
圖1圖1


圖2


在這裏插入圖片描述圖3


在這裏插入圖片描述圖4

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章