239. 奇偶游戏

题意:给你m个询问,每一个询问给出一个区间的左右端点和区间中的1的数量的奇偶性,输出不出现矛盾的最大的k值,即1k无矛盾,1k + 1矛盾。

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

方法2:扩展域并查集 + 离散化

扩展域写法:对于一个点x,把x拆分成两个域,奇数域和偶数域。
x的p域:是当x满足性质p的时候存在的域,这个域维护了和x满足性质p等价的其他信息。
本题对于x扩展出它的奇数域和偶数域
对于输入的x y odd, 检查如果x的偶数域和y的偶数域连通,或者x的奇数域和y的奇数域连通,说明出现矛盾。
否则就将x的奇数域和y的偶数域连通,x的偶数域和y的奇数域连通。

对于输入x y even, 检查如果x的奇数域和y的偶数域连通或者x的偶数域和y的奇数域连通,则出现矛盾。
否则把x的偶数域和y的偶数域,x的奇数域和y的奇数域连通。

注意:
在判断x y even时:
(x的奇数域和y的偶数域连通 || x的偶数和y的奇数域连通) <=> 出现矛盾
而 x的偶数域和y的偶数域不连通并不能说明出现了矛盾
因为(x的偶数域和y的偶数域不连通)不能决定(x的奇数域和y的偶数域连通)

同理在判断x y odd时:(x的奇数域和y的奇数域连通 || x的偶数和y的偶数域连通) <=> 出现矛盾
x的奇数域和y的偶数域不连通并不能说明出现了矛盾
因为(x的奇数域和y的偶数域不连通)不能决定(x的奇数域和y的奇数域连通)

#include<iostream>
#include<algorithm>

using namespace std;

const int M = 20010, N = 10010;

int m, n;
int p[M * 2];
int a[M], ak, k;

struct Node{
    int l, r;
    int x;
}query[N];
int qk;

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

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 main(){
    cin >> n >> m;
    
    while(m --){
        int l, r;
        string str;
        
        cin >> l >> r >> str;
        
        a[++ ak] = l - 1, a[++ ak] = r;
        query[++ qk] = {l - 1, r, str == "odd" ? 1 : 0};
    }
    
    sort(a + 1, a + 1 + ak);
    
    for(int i = 1; i <= ak; i ++)
        if(i == 1 || a[i] != a[i - 1]) a[++ k] = a[i];
    
    for(int i = 1; i <= k * 2; i ++) p[i] = i;
    
    int res = 0;
    
    for(int i = 1; i <= qk; i ++){
        int x = find(query[i].l), y = find(query[i].r);
        // x表示x的偶数域,x + k表示x的奇数域
        if(query[i].x){
            if(get(x) == get(y)) break;
            p[get(x + k)] = get(y);
            p[get(x)] = get(y + k);
        }else{
            if(get(x) == get(y + k)) break;
            p[get(x + k)] = get(y + k);
            p[get(x)] = get(y);
        }
        
        res ++;
    }
    
    cout << res;
    
    return 0;
}
原文地址:https://www.cnblogs.com/tomori/p/13896030.html