并查集的应用 方法1.维护节点 到 根节点的距离 方法2:扩展与并查集: 奇偶游戏

  • Post author:
  • Post category:其他


题目链接:

https://www.acwing.com/problem/content/description/241/

题目:

小 A 和小 B 在玩一个游戏。

首先,小 A 写了一个由 0 和 1 组成的序列 S,长度为 N。

然后,小 B 向小 A 提出了 M 个问题。

在每个问题中,小 B 指定两个数 l 和 r,小 A 回答 S[l∼r] 中有奇数个 1 还是偶数个 1。

机智的小 B 发现小 A 有可能在撒谎。

例如,小 A 曾经回答过 S[1∼3] 中有奇数个 1,S[4∼6] 中有偶数个 1,现在又回答 S[1∼6] 中有偶数个 1,显然这是自相矛盾的。

请你帮助小 B 检查这 M 个答案,并指出在至少多少个回答之后可以确定小 A 一定在撒谎。

即求出一个最小的 k,使得 01 序列 S 满足第 1∼k个回答,但不满足第 1∼k+1 个回答。

输入格式

第一行包含一个整数 N,表示 01 序列长度。

第二行包含一个整数 M,表示问题数量。

接下来 M 行,每行包含一组问答:两个整数 l 和 r,以及回答

even



odd

,用以描述 S[l∼r]中有偶数个 1还是奇数个 1。

输出格式

输出一个整数 k,表示 01 序列满足第 1∼k个回答,但不满足第 1∼k+1 个回答,如果 01 序列满足所有回答,则输出问题总数量。

数据范围

N≤1e9,M≤10000

输入样例:

10
5
1 2 even
3 4 odd
5 6 even
1 6 even
7 10 odd

输出样例:

3

方式1:并查集维护节点到根节点的距离,从而判断是否为同一类 (与之前并查集之 “食物链”的题目类似)

首先根据题目可以发现,N很大,1e9。 但是所用到的节点数为 2 * M , 2e4左右

所以可以想到与前面的并查集的应用之 “程序自动分析”一样 要用到离散化。

链接:

https://blog.csdn.net/qq_49120553/article/details/118760820?spm=1001.2014.3001.5501

同时可以发现此题的离散化 与 “程序自动分析”一样,不需要考虑离散化的值的顺序。

只是判断离散化的值 x 与 p[x]是否相同来判断是否为根节点,或者p[x]与p[y]是否在同一集合等等操作。  所以与顺序无关,使用哈希unordered_map  进行离散化即可。

而怎么使用并查集呢?

输入中会给出l,r 之间1的个数是奇数还是偶数。并且字符串只有1和0两部分组成。

那么我们可以使用前缀和的思想,如 l~r之间1的个数为奇数的话,

则 s[r] – s[l – 1]为奇数。  而这个的含义就是s[r] 与 s[l-1]一个为奇数一个为偶数。

同理 ,l~r之间1的个数为偶数的话

则s[r] – s[l – 1]为偶数,而这个的含义就是s[r] 与s[l – 1]要么两个都为奇数,要么两个都为偶数。

所以,也就可以通过奇偶性,如 l ~ r为奇数, 则s[r]与s[l – 1]不同类, l ~ r为偶数,则s[r]与 s[l – 1]同类 进行判断。


但是,这样的猜想是否正确呢?奇数个1,则两个不同类,偶数个1,则两个为同一类。


是正确的。因为我们可以发现,题目中并没有给出具体的字符串的形式。 所以只要你给出一个s[i]为奇数,或者偶数, s[ i – 1]为一个奇数 或者 偶数, 就可以构造出一种方式使得其成立


如: s[i]为奇数, s[i – 1]为偶数,也就是前 i – 1个中有偶数个1, 前i个中有奇数个1, 所以可以根据s[i],s[i – 1]的情况构造出一个a[i] 为 1 , 使得其成立


同样,s[i]为奇数,s[ i – 1]为奇数, 则a[i]为0。


所以,给出一个s[r], s[l]就一定至少一种方式可以构造出一个满足此s[r],s[l]的字符串出来。


所以此题就是用来判断,s[r] 与 s[l – 1]  是否为同类, 如前面推出s[r] 与 s[l – 1]为同类, 后面推出  s[r] 与 s[l – 1]不同类 ,则矛盾。

而判断两个节点是否同类就可以使用并查集。

分析:并查集 维护 节点到根节点的使用(与食物链题目类似)

应该怎么维护 根节点 到 节点 之间的距离呢?使用d[i],进行维护。

其中d[i]的含义是i节点到根节点之间的距离。

情况如下:

1.如果题目给出l~r中有偶数个1

则s[r] , s[l – 1]两者同类。

(1)r,l – 1在同一集合中,则d[r] + d[l – 1]为偶数即可。

分析:因为两个数相加,奇数 + 奇数 或者 偶数 + 偶数 才能为偶数

(2)不在同一集合中, 则将 l – 1的集合 并到  r的集合中去

所以d[p[l- 1]] = d[r] + d[l – 1]

2.如果题目给出l~r中有奇数个1

则s[r] , s[l – 1]两者不同类。

(1)r,l – 1在同一集合中,则d[r] + d[l – 1]为奇数即可。

分析:因为两个数相加,偶数 + 奇数 才为奇数

(2)不在同一集合中, 则将 l – 1的集合 并到  r的集合中去

所以d[p[l- 1]] = d[r] + d[l – 1] – 1即可

代码:

# include <iostream>
# include <string>
# include <unordered_map>
using namespace std;
const int N = 20010;

int p[N];
int d[N];  // d[i]为 i节点 到 父结点 的距离。
unordered_map<int,int> h;
int idx;

int n,m;

int get(int x)
{
    if(!h.count(x))
    {
        h[x] = ++idx;
    }
    return h[x];
}

int find2(int x)
{
    if(p[x] != x)
    {
        int t = p[x];
        p[x] = find2(p[x]);
        d[x] += d[t];
    }
    return p[x];
}

int main()
{
    scanf("%d %d",&n,&m);
    for(int i = 1 ; i <= 2 * m ; i++)
    {
        p[i] = i;
        d[i] = 0;  
    }
    
    int cnt = m;
    for(int i = 1 ; i <= m ; i++)
    {
        int a,b;
        string ch;
        cin >> a >> b >> ch;
        
        int x = get(a - 1); // 因为l~r中1的个数,则s[r] - s[l - 1]为奇数
        int y = get(b);
        
        int px = find2(x);
        int py = find2(y);
        if(ch == "even")  // 偶数
        {
            if(p[px] == p[py])
            {
                if( (d[x] + d[y]) % 2 != 0) // 不为偶数
                {
                    cnt = i - 1;
                    break;
                }
            }
            else  //让x集合 接到 y的集合上
            {
                d[px] = d[x] + d[y];
                p[px] = p[py];
            }
        }
        else
        {
            if(p[px] == p[py])
            {
                if( (d[x] + d[y]) % 2 == 0)
                {
                    cnt = i - 1;
                    break;
                }
            }
            else
            {
                d[px] = d[x] + d[y] - 1; 
                p[px] = p[py];
            }
        }
    }
    printf("%d\n",cnt);
    return 0;
}

方式2:扩展域并查集

扩展域并查集有一个核心的思路就是:合并多个条件,如果某个条件在这个集合中成立,则这个集合中的其他条件必须全部成立, 如果某个条件在这个集合中不成立, 则这个集合中的其他条件是否成立则无所谓。

而方式1则主要是合并两个节点,两个节点之间的关系则需要通过距离进行判断。


用不同的数值代表状态。r代表s[r]为偶数的情况,r + m 为 s[r]为奇数的情况

所以如:

(1)s[r] 与 s[l – 1] 是同类,

则合并r 与 l – 1. 并且合并 r + m , l – 1 + m.

即 合并s[r]为偶数,s[l – 1]也为偶数的情况,  s[r]为奇数,s[l – 1]为奇数的情况。

(2) s[r] 与 s[l – 1]不是同类

则合并 r 与 l – 1 + m,  并且合并 r + m 与 l – 1

即 合并s[r]为偶数,s[l – 1]为奇数的情况,  s[r]为奇数,s[l – 1]为偶数的情况。

这样合并后如何判断所给条件是否矛盾呢?

就用到了,集合中某个条件成立,则其他条件必须成立,  集合中某个条件不成立,则其他条件无所谓的性质。

s[r] 与 s[l – 1] 是同类

则说明 r,l – 1 在一个集合中,  r + m , l – 1 + m 在一个集合中,

于是从中任意判断一个:如 p[r] 与 p[l – 1 + m] 是否在同一集合中。

(1)如果在同一集合中,则 r + m , 与 l – 1 + m 不在同一集合中,发生矛盾。

则说明 r 与 l – 1不是同类, 条件有矛盾。

(2)如果r 与 l – 1 + m  不在同一集合中, 则其他条件无所谓,因为其他的条件也一定不在同一集合中, 无法进行判断, 所以进行赋值,让它们在同一集合中即可,用于判断后面条件与当下这个条件以后是否会矛盾。

代码实现:

# include <iostream>
# include <unordered_map>
using namespace std;
const int N = 40010;

unordered_map<int,int> h;
int idx;

int p[N];

int get(int x)
{
    if(h.count(x) == 0)
    {
        h[x] = ++idx;
    }
    return h[x];
}

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

// x,y分别代表偶数, x + n , y + n 代表x,y分别为奇数

int main()
{
    int n,m;
    scanf("%d %d",&n,&m);
    for(int i = 1 ; i <= 4 * m ; i++)  //所有的l,r都会被离散化到M 以内,l和 l + m , r 和 r + m 共4种值,所以开4倍
    {
        p[i] = i;
    }
    int cnt = m;
    for(int i =1 ; i <= m ; i++)
    {
        int l ,r;
        string ch;
        cin >> l >> r >> ch;
        int x = get(l - 1);
        int y = get(r);
        if(ch == "even") //偶数
        {
            if(find2(x) == find2(y + m))
            {
                cnt = i - 1;
                break;
            }
            else // 实际不用加else,但加else更容易让我自己看懂其中逻辑
            {
                p[find2(x)] = p[find2(y)];
                p[find2(x + m)] = p[find2(y + m)];
            }
        }
        else  // 奇数,不同类
        {
            if(find2(x) == find2(y)) // 矛盾
            {
                cnt= i - 1;
                break;
            }
            else
            {
                p[find2(x + m)] = p[find2(y)];
                p[find2(x)] = p[find2(y + m)];
            }
        }
    }
    printf("%d\n",cnt);
    return 0;
}



版权声明:本文为qq_49120553原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。