【ACWing】239. 奇偶游戏

题目地址:

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

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,以及回答even或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 ≤ 10000 N≤10^9,M≤10000 N109,M10000

考虑序列的前缀和数组 p p p,如果 S [ l : r ] S[l:r] S[l:r]中有奇数个 1 1 1,那么 p [ r ] p[r] p[r] p [ l − 1 ] p[l-1] p[l1]奇偶性不同,否则奇偶性相同。我们可以通过小 A A A的回答将 N N N个前缀和按奇偶性分类,如果某个时刻小 A A A的回答使得某两个前缀和的奇偶性有矛盾,则可以得出他在说谎。如果没有矛盾,则可以将涉及的 S [ k i ] S[k_i] S[ki]列出,每次考虑 S [ k i ] S[k_i] S[ki] S [ k i + 1 ] S[k_{i+1}] S[ki+1]的奇偶性,如果不能判断,则 A [ k i + 1 : k i + 1 ] A[k_i+1:k_{i+1}] A[ki+1:ki+1]的值可以任意指定,否则可以随意指定,只要使得 ∑ A [ k i + 1 : k i + 1 ] \sum A[k_i+1:k_{i+1}] A[ki+1:ki+1]符合 S [ k i ] S[k_i] S[ki] S [ k i + 1 ] S[k_{i+1}] S[ki+1]的奇偶性情形,这是一定可以做到的,所以如果奇偶性没有矛盾,不能得出小 A A A一定说谎的结论。所以我们只需判断奇偶性有没有矛盾就行了。

考虑开一个并查集,维护奇偶性关系能够被确定的前缀和(很显然”能确定奇偶性关系“这个关系是个等价关系),并对并查集加上边权,边权为 0 0 0代表边的两个端点奇偶性相同,边权为 1 1 1代表边的两个端点奇偶性不同,以 d [ x ] d[x] d[x]数组存储 x x x与其父亲的奇偶性关系的情况。对于每次询问 l , r l,r l,r,我们就能确定 p [ l − 1 ] p[l-1] p[l1] p [ r ] p[r] p[r]的奇偶性是否相同,接着查看一下 x = p [ l − 1 ] x=p[l-1] x=p[l1] y = p [ r ] y=p[r] y=p[r]在各自集合里的树根:
1、如果树根相同,则说明之前小 A A A的回答是能够确定 x x x y y y的奇偶性关系的,路径压缩之后 d [ x ] d[x] d[x] d [ y ] d[y] d[y]就分别存了 x x x y y y各自相对于树根的奇偶性关系,如果 d [ x ] ∧ d [ y ] = 0 d[x]\land d[y]=0 d[x]d[y]=0,则说明 x x x y y y的奇偶性相同,否则说明不同;开一个变量 t t t,定义其为如果当前小 A A A的回答是 x x x y y y奇偶性不同,则 t = 1 t=1 t=1,否则 t = 0 t=0 t=0,那么如果 d [ x ] ∧ d [ y ] ≠ t d[x]\land d[y]\ne t d[x]d[y]=t,则说明有矛盾,就能输出答案了;否则说明没有矛盾,继续看小 A A A的应答。
2、如果树根不同,则说明之前小 A A A的回答不能确定 x x x y y y的奇偶性关系,而当前小 A A A的回答可以确定关系,我们假设小 A A A说的为真,那么在路径压缩之后, p x p_x px p y p_y py的奇偶性关系就可以由 d [ x ] d[x] d[x] d [ y ] d[y] d[y]确定了。不妨我们要把 x x x合并进 y y y(即 p x p_x px指向 p y p_y py),开一个变量 t t t,意思同上,那么有 d [ x ] ∧ d [ y ] ∧ d [ p x ] = t d[x]\land d[y]\land d[p_x]=t d[x]d[y]d[px]=t,所以有 d [ p x ] = d [ x ] ∧ d [ y ] ∧ t d[p_x]=d[x]\land d[y]\land t d[px]=d[x]d[y]t,即可以人为规定 d [ p x ] = d [ x ] ∧ d [ y ] ∧ t d[p_x]=d[x]\land d[y]\land t d[px]=d[x]d[y]t,就可以使得小 A A A说的话为真。

执行并查集里的查树根的操作需要维护 d d d的定义。并且,由于本题 M M M的范围远小于 N N N的上界,所以要离散化。代码如下:

#include <iostream>
#include <cstring>
#include <unordered_map>
using namespace std;

const int N = 1e4 + 10;
int n, m;
int p[N], d[N];
unordered_map<int, int> mp;

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

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

    return p[x];
}

int main() {
    
    
    scanf("%d%d", &n, &m);
    for (int i = 0; i < N; i++) p[i] = i;

    n = 0;
    
    int res = m;
    for (int i = 1; i <= m; i++) {
    
    
        int a, b;
        char type[5];
        scanf("%d%d%s", &a, &b, type);
        a = get(a - 1), b = get(b);

		// 如果奇偶性相同,则定义t = 0,否则t = 1
        int t = 0;
        if (type[0] == 'o') t = 1;

        int pa = find(a), pb = find(b);
        if (pa == pb) {
    
    
            if ((d[a] ^ d[b]) != t) {
    
    
                res = i - 1;
                break;
            }
        } else {
    
    
            p[pa] = pb;
            d[pa] = d[a] ^ d[b] ^ t;
        }
    }

    printf("%d\n", res);

    return 0;
}

时间复杂度 O ( m log ⁡ ∗ m ) O(m\log ^*m) O(mlogm),空间 O ( m ) O(m) O(m)

也可以用所谓的”扩展域“并查集来做。对所有产生的 p [ k ] p[k] p[k],考虑两个布尔值,即 p [ k ] p[k] p[k]为奇和为偶。那么一共会产生不到 2 M 2M 2M个布尔值,在这些布尔值上定义等价关系,若 b 1 = b 2 b_1=b_2 b1=b2则它们等价(即若它们同真假则它们等价),每次小 A A A回答询问的时候,都会确定两对布尔值是同真假。例如,如果 x x x y y y同奇偶,那么 x x x为奇和 y y y为奇,这两个布尔值同真假;那么 x x x为偶和 y y y为偶,这两个布尔值也同真假;如果 x x x y y y不同奇偶,那么 x x x为奇和 y y y为偶,这两个布尔值同真假;并且 x x x为偶和 y y y为奇,这两个布尔值也同真假。如果某次小 A A A的回答会导致某两个布尔值原本同真假,但现在发生了矛盾,则说明小 A A A在说谎。代码如下:

#include <iostream>
#include <cstring>
#include <unordered_map>
using namespace std;

const int N = (1e4 + 10) * 2, M = 1e4 + 10;
int n, m;
int p[N];
unordered_map<int, int> mp;

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

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

int main() {
    
    
    scanf("%d%d", &n, &m);
    for (int i = 0; i < N; i++) p[i] = i;

    n = 0;
    
    int res = m;
    for (int i = 1; i <= m; i++) {
    
    
        int a, b;
        char type[5];
        scanf("%d%d%s", &a, &b, type);
        a = get(a - 1), b = get(b);

        if (type[0] == 'e') {
    
    
        	// 如果a和b不同奇偶,但小A的回答说a与b同奇偶,
        	// 说明小A在说谎
            if (find(a) == find(b + M)) {
    
    
                res = i - 1;
                break;
            }
            p[find(a)] = find(b);
            p[find(a + M)] = find(b + M);
        } else {
    
    
            if (find(a) == find(b)) {
    
    
                res = i - 1;
                break;
            }
            p[find(a)] = find(b + M);
            p[find(a + M)] = find(b);
        }
    }

    printf("%d\n", res);

    return 0;
}

时空复杂度一样。

猜你喜欢

转载自blog.csdn.net/qq_46105170/article/details/119738396