杭電3234——帶權並差集(異或運算)
寫在題前:
- 拿到題的時候,woc,這是個什麼鬼題哦!
- 剛開始不知道異或的一些運算,所以沒怎麼有思路,後來找了異或公式,還是沒怎麼有思路,哈哈哈。
- 有用的異或公式:a ^ b ^ b ^ c ^ b ^ c ^ b ^ c = a ^ c 。(就是可以消去兩個相同的元素,有點消消樂的感覺)
- 可以搜一下異或運算的應用,比如說:判斷重複元素等等。還是挺有意思的。
解題思路:
帶權並差集:權值就是兩個節點的異或值。
- 判斷需要進行的操作
- I p v :就把 p 和虛擬節點n建立聯繫(如果這個集合裏面有n,說明這個集合能計算出具體的異或值)。
- I p q v :把 p 和 q 建立聯繫,權值爲v
- 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
圖2
圖3
圖4