1. 程式人生 > 實用技巧 >239. 奇偶遊戲

239. 奇偶遊戲

題意:給你m個詢問,每一個詢問給出一個區間的左右端點和區間中的1的數量的奇偶性,輸出不出現矛盾的最大的k值,即1~k無矛盾,1~k + 1矛盾。
方法:帶權並查集 + 離散化
設區間左右端點a和b,01序列的字首和陣列為s,那麼[a, b]中的1的個數cnt = s[b] - s[a - 1], 那麼cnt是奇是偶和s[b] - s[a - 1]有關:

  1. s[b]和s[a - 1]同奇偶,cnt為偶數
  2. s[b]和s[a - 1]不同奇偶,cnt為奇數
    那麼對於輸入的a b odd/even, 可以將他看成是s[b]和s[a - 1]的奇偶性,如果是a b odd則s[b]和s[a - 1]的奇偶性不同反之相同, 簡單起見,把s[b]和s[a - 1]的奇偶關係之間看成是點b
    點a - 1的奇偶關係。

這樣就可以通過並查集來維護已經確定奇偶關係的點了。

設點x,d[x]表示點x和它所在樹的根之間的奇偶關係(同0,異1),所以兩個點x和y的奇偶關係應該是,在x和y在同一棵樹中時,它們和根的奇偶關係的異或,即:d[x] ^ d[y]

當x和y不再同一棵樹中時,需要根據題目給出的x和y的奇偶關係來合併x所在的樹和y所在的樹,合併x和y所在的樹的關鍵問題就是得到x所在樹的樹根px和y所在樹的樹根py之間的奇偶關係,這樣在令p[px] = py後,才能進一步得到x所在樹的所有結點和py的奇偶關係。

求x所在樹的樹根px和y所在樹的樹根py之間的奇偶關係:設輸入的x和y的奇偶關係為ans(同奇,同偶為0,否則為1),可以知道在合併完x和y所在的樹後,ans = d[x] ^ d[px] ^ d[y],所以可得:d[px] = d[x] ^ d[y] ^ ans,所以在合併x和y所在的樹時,需要將d[px]賦值為d[x] ^ d[y] ^ ans,表示原本x所在樹的根和y所在樹的根之間的奇偶性關係。

注意:合併x和y所在的樹是在輸入了x和y的奇偶關係,並且x和y不再同一棵樹中才進行的,如果x和y已經在同一棵樹中,那麼就應該對這個query進行檢驗。

#include<iostream>
#include<vector>
#include<algorithm>

using namespace std;

const int M = 20010;

struct Node{
    int l, r;
    string s;
};

int p[M], d[M];
vector<Node> query;
int a[M], t;
int n, m;
int k;

int find(int x){
    int l = 1, r = k;
    while(l < r){
        int mid = l + r >> 1;
        if(a[mid] >= x) r = mid;
        else l = mid + 1;
    }
    
    return l;
}

int get(int x){
    if(p[x] != x){
        int root = get(p[x]);
        d[x] = d[x] ^ d[p[x]];
        p[x] = root;
    }
    
    return p[x];
}

int merge(int x, int y, int e){
    int t = d[x] ^ d[y];
    x = get(x), y = get(y);
    p[x] = y, d[x] = t ^ e;
}

int main(){
    cin >> n >> m;
    
    for(int i = 1; i <= m; i ++){
        int l, r;
        string s;
        cin >> l >> r >> s;
        
        a[++ t] = l - 1; // 注意雖然輸入的是 l r s, 但其實給出的是點r和點l - 1的奇偶關係
        a[++ t] = r;
        
        query.push_back({l - 1, r, s});
    }
    
    sort(a + 1, a + t + 1);
    
    for(int i = 1; i <= t; i ++)
        if(i == 1 || a[i] != a[k]) a[++ k] = a[i];
    
    
    for(int i = 1; i <= k; i ++) p[i] = i;
    
    int res = 0;
    
    for(int i = 0; i < query.size(); i ++){
        string s = query[i].s;
        int u = find(query[i].l), v = find(query[i].r);
        
        if(get(u) != get(v)) merge(u, v, s == "odd" ? 1 : 0);
        else if(s == "even" && d[u] ^ d[v]) break;
        else if(s == "odd" && d[u] ^ d[v] == 0) break;
        res ++;
    }
    
    cout << res;
    
    return 0;
}