「带权并查集」奇偶游戏

奇偶游戏

原题链接:奇偶游戏

题目大意

给你N个区间,每个区间给你它含1的奇偶性,问你哪些询问逻辑上冲突

题目题解

一道带权并查集的题,让我对带权并查集有了更深入的理解,带权并查集可以分为两种(在这道题中)

  • “边带权”并查集
  • “扩展域”并查集

两种方法都是思维上的不同所造成的,其中第一种解法是最常见的,第二种解法在代码实现上是最简单的,我们先来对第一种进行探究

边带权,很明显,我们要在并查集的边上进行一个储存边权的操作,我们这里用d来表示当前节点到根节点的Xor值,什么意思,又如何维护呢?意思就是我们对于每条边储存的是连接的两点是否属于同一奇偶性,边权为0则相同,为1则不同,那么我们从x节点到根节点的唯一路径上所有的边权全部Xor起来,就能判断x节点和根节点是否为同一奇偶性了,这样的话我们就能够判断对于任意两点而言,x节点到根节点的Xor值和y节点到根节点的Xor值Xor起来得到的值,就是我们所需要的对于任意两点的Xor值,再和我们当前询问的奇偶性进行Xor,如果为1则说明冲突,如果为0则说明不冲突

那既然我们的查询解决完了,我们就要解决合并的问题了,接着上一段的最后继续说,我们当前有x, y两个点,和他们的根p, q,令连接后p为q的子节点,那么我们现在要求的就是d[p]了,d[p]怎么求?我们现在已知的信息是d[x] 和 d[y] 也就是 x~p 和 y~q,我们现在要求的是p~q,我们已知x~y是由 x~p,y~q,p~q 来组成的,那么 x y 的奇偶性 (我们这里用\(cnt_{x, y}\)来表示任意两点的奇偶性,这个奇偶性怎么来了?输入就给了) \(cnt_{x,y} = d[x]Xord[y]Xord[p]\) 那么我们就可以得到\(d[p] = d[x] Xord[y]Xorans_{x, y}\) 了,于是合并操作也就完成了。

进行完这两个之后,我们就对题本身进行分析一下,因为本题中给的是一个区间,而并非一个点,所以我们还得将区间转化为点,区间转化为点?感觉很熟悉?对,的确很熟悉 我们在 防线 这道题中用过类似的方法,就是用前缀和保存奇偶性,什么意思?详细可以看上面那道题,我这里就不再赘述,转化到本题上来,我们既然要转化到点,这个数据范围肯定是不行的,我们考虑离散化。

那么离散化之后,我们就得到了一个序列,这个序列的点是紧紧挨着的,我们这里就能够通过上面的区间转点来搞定了,比如我们现在要合并一个区间 \(l, r\),那实际上 \(l,r\) 的奇偶性已经是已知的了,(很多人卡在这里,不知道这里要怎么进行操作,也有很多人没想到用并查集来维护..,卡在套路上了),那么\(l - 1\) 到前面的某一个值的奇偶性一定也是已知的,那么我们就将 \(r\) 和 \(l - 1\) 用并查集维护(想想为什么?答案很显然),这样我们就维护了两个区间的奇偶性并且将两个区间合并。

根据上面的信息,我们就完成了这道题,代码如下

//#define fre yes

#include <cstdio>
#include <algorithm>

const int N = 20005;
int ele[N << 1];
struct message {
    int l, r, ans;
} arr[N];

bool flag;
namespace Union {
    int par[N], d[N];
    inline void init(int n) {
        for (int i = 1; i <= n; i++) {
            par[i] = i;
        }
    }

    int find(int x) {
        if(par[x] == x) return par[x];
        int rt = find(par[x]);
        d[x] ^= d[par[x]];
        return par[x] = rt;
    }

    inline void unite(int x, int y, int a, int b, int i) {
        par[a] = b;
        d[a] = d[x] ^ d[y] ^ arr[i].ans;
    }

    inline void solve(int x, int y, int i) {
        if((d[x] ^ d[y]) != arr[i].ans) {
            printf("%d\n", i - 1);
            flag = 1;
        }
    }
}

int m;
inline void discrete(int n) {
    std::sort(ele + 1, ele + 1 + n * 2);
    m = std::unique(ele + 1, ele + 1 + n * 2) - ele - 1;
}

int ask(int x) {
    return std::lower_bound(ele + 1, ele + 1 + m, x) - ele;
}

int main() {
    static int n, T;
    scanf("%d %d", &T, &n);
    for (int i = 1; i <= n; i++) {
        int x, y; char c[6];
        scanf("%d %d %s", &x, &y, c + 1);
        arr[i].l = x; arr[i].r = y;
        arr[i].ans = (c[1] == 'e' ? 0 : 1);
        ele[i] = x - 1; ele[i + n] = y;
    } 

    discrete(2 * n);
    Union::init(m);

    for (int i = 1; i <= n; i++) {
        int x = ask(arr[i].l - 1);
        int y = ask(arr[i].r);
        int p = Union::find(x), q = Union::find(y);
        if(p == q) {
            Union::solve(x, y, i);
            if(flag) return 0;
        } else Union::unite(x, y, p, q, i);
    } printf("%d\n", n);
    return 0;
}

然后我们再来讨论第二种方法,扩展域并查集,第二种方法思维上更简单,代码上更容易实现,因为不会涉及到位运算等知识,怎么来运作的呢?

将每个节点\(x\)拆成两个节点,\(x_{odd}\) 与 \(x_{even}\) 分别表示sum[x]为奇数和sum[x]为偶数,我们经常也把这两个节点成为x的“奇数域”和“偶数域”。

那么就会有以下情况,设离散化后的\(l - 1\) 与 \(r\) 的值分别是 \(x\) 和 \(y\) ,设 \(cnt\) 为两点的奇偶性(这里只是读入的)

  • 若 cnt = 0 则合并 \(x_{odd}\) 与 \(y_{odd}\) ,\(x_{even}\) 与 \(y_{even}\) 。这表示"x为奇数"与"y为奇数"可以互相推出,"x为偶数" 与 "y为偶数"也可以互相推出,它们是等价的信息 .
  • 若 cnt = 0 则合并 \(x_{odd}\) 与 \(y_{even}\),\(x_{even}\) 与 \(y_{odd}\) 。 这表示"x为奇数"与"y为偶数"可以互相推出,"x为偶数" 与 "y为奇数" 也可以互相推出,它们是等价的信息。(为何能够相互推出?可以自己想想)

上述合并也维护了关系的传递性,在处理完(x, y, 0)与(y, z, 1)两个关系之后,很显然 x, z的关系也就明显了,这种做法就相当于在无向图上维护节点之间的连通情况,只是拓展了多个域来应对多种传递关系

若 x, y 对应 \(x_{odd}\) 与 \(y_{odd}\) 在同一集合内,就说明二者奇偶性相同,若 x, y 对应 \(x_{odd}\) 与 \(y_{even}\) 在同一集合内,则已知二者奇偶性不同

那么以上信息,我们就可得代码,代码如下

//#define fre yes

#include <cstdio>
#include <algorithm>

const int N = 20005;
int ele[N << 1];
struct message {
    int l, r, ans;
} arr[N];

bool flag;
namespace Union {
    int par[N], d[N];
    inline void init(int n) {
        for (int i = 1; i <= n; i++) {
            par[i] = i;
        }
    }

    int find(int x) {
        if(par[x] == x) return par[x];
        return par[x] = find(par[x]);
    }

    inline void unite(int x, int y) {
        int a = find(x);
        int b = find(y);
        if(a == b) return ;

        par[a] = b;
    }
}

int m;
inline void discrete(int n) {
    std::sort(ele + 1, ele + 1 + n * 2);
    m = std::unique(ele + 1, ele + 1 + n * 2) - ele - 1;
}

int ask(int x) {
    return std::lower_bound(ele + 1, ele + 1 + m, x) - ele;
}

int main() {
    static int n, T;
    scanf("%d %d", &T, &n);
    for (int i = 1; i <= n; i++) {
        int x, y; char c[6];
        scanf("%d %d %s", &x, &y, c + 1);
        arr[i].l = x; arr[i].r = y;
        arr[i].ans = (c[1] == 'e' ? 0 : 1);
        ele[i] = x - 1; ele[i + n] = y;
    } 

    discrete(2 * n);
    Union::init(m * 2);

    for (int i = 1; i <= n; i++) {
        int x = ask(arr[i].l - 1);
        int y = ask(arr[i].r);
        int x_odd = x, x_even = x + m;
        int y_odd = y, y_even = y + m;
        if(arr[i].ans == 0) {
            if(Union::find(x_odd) == Union::find(y_even)) {
                printf("%d\n", i - 1);
                return 0;
            }

            Union::unite(x_odd, y_odd);
            Union::unite(x_even, y_even);
        } else {
            if(Union::find(x_odd) == Union::find(y_odd)) {
                printf("%d\n", i - 1);
                return 0;
            }

            Union::unite(x_odd, y_even);
            Union::unite(x_even, y_odd);
        }
    } printf("%d\n", n);
    return 0;
}

原文地址:https://www.cnblogs.com/Nicoppa/p/11576748.html

时间: 2024-08-03 02:52:14

「带权并查集」奇偶游戏的相关文章

「UVA 11987」Almost Union-Find 「带权并查集」「思维」

你发现,这个第二个操作不可能用普通并查集来搞,很棘手 但是你考虑一下,并查集维护的是个森林结构,并且路径压缩的时候每个森林的根是不会变的, 也就是意味着每删掉一个点你需要让他的踪影消失匿迹即可,并不需要让他在原有的树结构上消失. 具体怎么消失?把贡献全在根上减掉即可,再新建一个新点连进去. 这个新点可以用id数组表示,即id[x]为x节点现在的编号. #include <bits/stdc++.h> #define test(...) fprintf(stderr, __VA_ARGS__)

【带权并查集】【离散化】vijos P1112 小胖的奇偶

每个区间拆成r和l-1两个端点,若之内有偶数个1,则这两个端点对应的前缀的奇偶性必须相同,否则必须相反. 于是可以用带权并查集维护,每个结点储存其与其父节点的奇偶性是否相同,并且在路径压缩以及Union时进行分类讨论即可. 由于n太大,要对两个端点进行离散化. #include<cstdio> #include<algorithm> using namespace std; int fa[10010]; bool rel[10010]; int findroot(int x){ i

hdu3038(带权并查集)

题目链接: http://acm.split.hdu.edu.cn/showproblem.php?pid=3038 题意: n表示有一个长度为n的数组, 接下来有m行形如x, y, d的输入, 表示从第x,个元素到第y个元素的和为d(包括x, 和y), 问m行输入里面有几个是错误的(第一个输入是正确的); 思路: 很显然带权并查集咯,我们可以用距离的概念代替和的概念比较好理解一点,d表示x到y的和即x到y的距离; 可以用rank[x]表示x到其父亲节点的距离,  将正确的距离关系合并到并查集中

【POJ1182】 食物链 (带权并查集)

Description 动物王国中有三类动物A,B,C,这三类动物的食物链构成了有趣的环形.A吃B, B吃C,C吃A. 现有N个动物,以1-N编号.每个动物都是A,B,C中的一种,但是我们并不知道它到底是哪一种. 有人用两种说法对这N个动物所构成的食物链关系进行描述: 第一种说法是"1 X Y",表示X和Y是同类. 第二种说法是"2 X Y",表示X吃Y. 此人对N个动物,用上述两种说法,一句接一句地说出K句话,这K句话有的是真的,有的是假的.当一句话满足下列三条之

【poj 1988】Cube Stacking(图论--带权并查集 模版题)

题意:有N个方块,M个操作{“C x”:查询方块x上的方块数:“M x y”:移动方块x所在的整个方块堆到方块y所在的整个方块堆之上}.输出相应的答案. 解法:带权并查集.每堆方块作为一个集合,维护3个数组:fa[x]表示x方块所在堆的最顶部的方块:d[x]表示x方块所在堆的最底部的方块:f[x]表示x方块方块x上的方块数. 1 #include<cstdio> 2 #include<cstdlib> 3 #include<cstring> 4 #include<

并查集练习2(带权并查集)

明天旅游去爬山逛庙玩,今天练一天然后早早睡觉啦~ poj1703 Find them, Catch them (带权并查集) 1 #include<cstdio> 2 const int N=1e5+1; 3 int f[N]; 4 int r[N];//表示与父节点的关系,0同类,1不同类 5 int n; 6 void init(){ 7 for(int i=1;i<=n;++i){ 8 f[i]=i; r[i]=0; 9 } 10 } 11 int fin(int x){ 12 i

Lightoj1009 Back to Underworld(带权并查集)

转载请注明出处: http://www.cnblogs.com/fraud/          ——by fraud Back to Underworld Time Limit:4000MS     Memory Limit:32768KB     64bit IO Format:%lld & %llu Description The Vampires and Lykans are fighting each other to death. The war has become so fierc

[NOIP摸你赛]Hzwer的陨石(带权并查集)

题目描述: 经过不懈的努力,Hzwer召唤了很多陨石.已知Hzwer的地图上共有n个区域,且一开始的时候第i个陨石掉在了第i个区域.有电力喷射背包的ndsf很自豪,他认为搬陨石很容易,所以他将一些区域的陨石全搬到了另外一些区域. 在ndsf愉快的搬运过程中,Hzwer想知道一些陨石的信息.对于Hzwer询问的每个陨石i,你必须告诉他,在当前这个时候,i号陨石在所在区域x.x区域共有的陨石数y.以及i号陨石被搬运的次数z. 输入描述: 输入的第一行是一个正整数T.表示有多少组输入数据. 接下来共有

hdu 1558 Segment set【基础带权并查集+计算几何】

Segment set Time Limit: 3000/1000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others) Total Submission(s): 3599    Accepted Submission(s): 1346 Problem Description A segment and all segments which are connected with it compose a segment set