算法竞赛进阶指南 0X40数据结构进阶——奇偶游戏

题目链接

AcWing 239. 奇偶游戏 mid

题目描述

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

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

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

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

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

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

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

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

输入格式

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

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

接下来 M M M 行,每行包含一组问答:两个整数 l l l r r r,以及回答 e v e n even even o d d odd odd,用以描述 S [ l ∼ r ] S[l∼r] S[lr] 中有偶数个 1 1 1 还是奇数个 1 1 1

输出格式

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

数据范围

N ≤ 1 0 9 , M ≤ 5000 N≤10^9,M≤5000 N109,M5000

输入样例:

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

输出样例:

3

解法:带权值的并查集

如果我们使用 s u m sum sum 来表示序列 S S S 的前缀和的话。

  • S [ l : r ] S[l :r] S[l:r]有偶数个 1 1 1,说明 s u m [ l − 1 ] sum[l - 1] sum[l1] s u m [ r ] sum[r] sum[r] 奇偶性相同;
  • S [ l : r ] S[l :r] S[l:r]有奇数个 1 1 1,说明 s u m [ l − 1 ] sum[l - 1] sum[l1] s u m [ r ] sum[r] sum[r] 奇偶性不同;

传递关系:

  • 如果 a a a b b b 奇偶性相同 b b b c c c 奇偶性相同,那么 a a a c c c 奇偶性也相同
  • 如果 a a a b b b 奇偶性相同 b b b c c c 奇偶性不同,那么 a a a c c c 奇偶性也不同
  • 如果 a a a b b b 奇偶性不同 b b b c c c 奇偶性不同,那么 a a a c c c 奇偶性也相同

因为 N N N 很大,而 M M M 比较小,所以我们还是使用离散化的技巧,将输入的数映射到 最大为 2 M 2M 2M 的数组中。在实现上,我们可以使用 map 来实现。

我们定义 d [ x ] d[x] d[x] x x x 节点到它的父结点 p [ x ] p[x] p[x] 的边权。

  • 如果 d [ x ] = 0 d[x] = 0 d[x]=0,表示节点 x x x 与它的父节点 p [ x ] p[x] p[x] 奇偶性相同;
  • 如果 d [ x ] = 1 d[x] = 1 d[x]=1,表示节点 x x x 与它的父节点 p [ x ] p[x] p[x] 奇偶性不同;

路径压缩:

在路径压缩的时候,对于 节点 x x x 到其根节点路径上的所有边权值做 异或运算,就可以得到 节点 x x x 与其 根节点的奇偶性关系。

在这里插入图片描述

在这里插入图片描述

对于 x x x y y y ,我们先检查它们是否在一个集合中,即 f i n d ( x ) = f i n d ( y ) find(x) = find(y) find(x)=find(y)

  1. 如果在同一个集合中。如果 d [ x ]   x o r   d [ y ] ≠ t y p e d[x]\ xor\ d[y] \neq type d[x] xor d[y]=type,说明这个回答有矛盾,则在这个问题之后就能够确定小 A A A 撒谎了。
  2. 如果不在同一个集合之中,就需要先合并两个集合。

我们设 p , q p , q p,q 分别是 x , y x,y x,y 的祖宗节点。

在这里插入图片描述

我们已知 d [ x ] d[x] d[x] d [ y ] d[y] d[y] 分别表示 路径 x ∼ p x \sim p xp y ∼ p y \sim p yp 之间的所有边权 异或和。

p ∼ q p \sim q pq 这一段路径的异或和 d [ p ] d[p] d[p] 是我们要求的 。

由于 x ∼ y x \sim y xy 的路径是由 x ∼ p , p ∼ q , q ∼ y x \sim p , p \sim q , q \sim y xp,pq,qy 组成的, x x x y y y 的奇偶性 t y p e type type 我们是已知的,因此 t y p e = d [ x ]   x o r   d [ y ]   x o r   d [ p ] type = d[x] \ xor \ d[y] \ xor \ d[p] type=d[x] xor d[y] xor d[p]

所以 d [ p ] = d [ x ]   x o r   d [ y ]   x o r   t y p e d[p] = d[x] \ xor \ d[y] \ xor \ type d[p]=d[x] xor d[y] xor type

在这里插入图片描述

时间复杂度: O ( n ) O(n) O(n)

C++代码:

#include<iostream>
#include<algorithm>
#include<map>

using namespace std;

const int N = 1e4+10;

struct{
    int l,r,type;
}query[N];

int n,m;

int p[N] , d[N] , idx = 0;

map<int,int> mp;

//输入离散化
void read_discrete(){
    cin>>n>>m;
    char str[5];
    
    for(int i = 1;i <= m;i++){
        
        scanf("%d%d%s",&query[i].l,&query[i].r,str);
        
        query[i].type = (str[0] == 'o' ? 1 : 0);
        
        int l = query[i].l - 1 , r = query[i].r;
        
        if(mp.find(l) == mp.end()) mp[l] = ++idx;
        if(mp.find(r) == mp.end()) mp[r] = ++idx;
    }
    
    n = idx;
}

//路径压缩找根节点
int find(int x){
    if(x == p[x]) return p[x];
    
    int root = find(p[x]);
    d[x] ^= d[p[x]];

    return p[x] = root;
}

int main(){
    read_discrete();
    
    //cout<<n<<'\n';
    
    for(int i = 1;i <= n;i++) p[i] = i;
    
    for(int i = 1;i <= m;i++){
        
        int l = query[i].l - 1 , r = query[i].r;
        
        int a = mp[l] , b = mp[r];
        
        int x = find(a) , y = find(b);
        
        if(x == y){
            //i轮发生矛盾,i - 1 轮及之前的都是没问题的 
            if((d[a] ^ d[b]) != query[i].type){
                cout<<i - 1<<'\n';
                return 0;
            }
        }
        
        else{
            p[x] = y;
            d[x] = d[a] ^ d[b] ^ query[i].type;
        }
    }
    
    //没有矛盾
    cout<<m<<'\n';
    return 0;
}

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值