AcWing-奇偶游戏-并查集

这篇博客主要介绍了如何使用带权并查集和扩展域求并查集的方法解决一个区间内1的数量奇偶性判断问题。通过离散化处理输入数据,利用前缀和思想与并查集结合,判断区间内的1数量是否符合给定的奇偶性条件,从而找出矛盾的行。文章详细解析了两种不同的实现策略,并给出了相应的C++代码实现。
摘要由CSDN通过智能技术生成

彼岸花开开彼岸 断肠草愁愁断肠 奈何桥前可奈何 三生石前定三生

 题意:给一个n表示一个01序列有多长,一个m,下面是m行,每行两个数字l, r,还有一个英文单词,表示l到r区间内有奇数还是偶数个1,求到第几行出现矛盾。矛盾即不合法,比如[1,4]内有偶数个1,[5, 8]内有奇数个1,但是下一行是[1,8]内有偶数个1,这显然是不合法的

做法一:带权并查集

首先看数据,发现可以用离散化,然后通过离散化处理输入的数据x,y。

unordered_map<int, int> s;
int n, m;
int get(int x)
{
    if(s.count(x) == 0) s[x] = ++n;
    return s[x];
}

 通过观察我们发现,可以通过前缀和的思想,比如s[l - r] 有偶数个1,那么sum[l-1]和sum[r]奇偶性相同,反之,s[l - r]有奇数个1,那么sum[l-1]和 sum[r]的奇偶性相反,因为sum[l-1]+s[l - r]=sum[r],然后奇+奇=偶,偶+偶=偶,奇+偶=奇。那我们的输入的数据,表示编号,表示前缀和。

然后我们用d数组记录并表示边权,用p数组表示并查集的首领,如果d[x] = 0,(这个x相当于前边的sum数组), 则表示x与p[x]的奇偶性相同,如果为1,则表示不同。那么在一个集合里我们就可以通过根节点知道所有的子节点之间的关系,比如x到root的距离为dx,y到root的距离为dy,那么(dx+dy)% 2就可以表示是否他俩性质相同了。然后相同为0,不同为1,我们就非常自然地想到了可以用异或来代替运算,加权并查集放于Find函数中

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

此处的^也可以写成+,因为奇+奇=偶,偶+偶=偶,奇+偶=奇,取模2效果是一样的。

下一步来判断矛盾:

如果x和y是同类

如果px==py,即他俩属于同一个集合,那么就可以通过根节点找子节点的关系,也就是dx+dy,如果结果与我们输入的字符串的状态一致,就可以return i-1,否则继续查找;dx+dy也可以写成dx^dy,因为dx+dy如果是奇数,那么就是dx和dy一个是奇数一个是偶数,也就是一个是1一个是0,取异或值的话,得到的结果是一样的;那么dx^dy==0时,说明xy是同类,然后 py与px是同类,所以不矛盾;反之如果dx^dy==1时,说明xy是同类,然后px与py不是同类,所以矛盾

如果px != py,即他们不属于一个集合,那么我们需要把他们拉进一个集合里,p[px] = py,这时px和py的d数组的关系也需要界定,那就是dx+?+dy=0,(0代表对2取模为0, 表示同类)也就是?+(dx+dy)= 0,那么就是?^dx^dy==0, 那么? == dx^dy^0(异或的一个公式)也就是d[px]=dx^dy^0(x的头儿的头儿是py,那么px到py的关系就是dx^dy^0)

如果x和y是异类

同上,当px==py时,dx^dy=1是不矛盾,dx^dy=0时,矛盾

当px!=py时,d[px] = dx^dy^1

然后就可以两类合并一起写,变成

#pragma GCC optimize(2)
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
#define mm(a) memset(a, 0, sizeof(a))
#define PI acos(-1)
#define LLu unsigned long long
#define PLL pair<ll, ll>
#define PII pair<int, int>
#define xx first 
#define yy second 
ll gcd(ll a, ll b) {return b ? gcd(b, a%b) : a; }
ll lcm(ll a, ll b) {return a/gcd(a, b)*b;}
const int INF = 0x3f3f3f3f;
const int N = 4e4 + 7;

int n, m;
int d[N], p[N];
unordered_map<int, int>s;

int get(int x)
{
    if(s.count(x) == 0) s[x] = ++n;
    return s[x];
}
int Find(int x)
{
    if(x != p[x])
    {
        int root = Find(p[x]);
        d[x] ^= d[p[x]];
        p[x] = root;
    }
    return p[x];
}
int main()
{
    cin >> n >> m;
    n = 0;
    for(int i = 0; i <= N; i ++) p[i] = i;
    int res = m;
    for(int i = 1; i <= m; i ++)
    {
        int x, y;
        string type;
        cin >> x >> y >> type;
        x = get(x - 1), y = get(y);
        int t = 0;
        if(type == "odd") t = 1;
        int px = Find(x), py = Find(y);
        if(px == py)
        {
            if(d[x]^d[y] != t)
            {
                res = i - 1;
                break;
            }   
        }
        else
        {
            p[px] = py;
            d[px] = d[x]^d[y]^t;
        }
    }
    cout << res << endl;
    return 0;
}

做法二:扩展域求并查集

好哒,我们再次观察数据,发现他又大又小,所以可以用离散化,然后数据就被缩小了,那么我们就可以考虑枚举的思想。两种状态奇偶,也就是一个点有两种状态,那么我们就可以用并查集反集来拆点,表示两种状态。操作就是取Base=边界值N,然后N~2*N范围内就是与我们的已知点状态相反的状态(所以数组要开两倍)

开始枚举

如果输入的字符串表示偶数,也就是x的状态等于y的状态,那么如果x的反状态等于y则矛盾,所以

if (find(a + Base) == find(b)){
    res = i - 1;
    break;
}

如果x的反状态不等于y的状态,也就是x的状态等于y的状态,那么就把x和y合并,也就是

p[find(a)] = find(b);
p[find(a + Base)] = find(b + Base);

如果输入的字符串表示奇数,同上

当x的状态等于y的状态时矛盾

if (find(a) == find(b))
{
    res = i - 1;
    break;
}

否则,x的状态与y的反状态合并,x的反状态与y的状态合并,因为x与y是相反的,那么x就与y的反状态是一致的,对于y也一样

p[find(a + Base)] = find(b);
p[find(a)] = find(b + Base);

最后的代码:

#pragma GCC optimize(2)
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
#define mm(a) memset(a, 0, sizeof(a))
#define PI acos(-1)
#define LLu unsigned long long
#define PLL pair<ll, ll>
#define PII pair<int, int>
#define xx first 
#define yy second 
ll gcd(ll a, ll b) {return b ? gcd(b, a%b) : a; }
ll lcm(ll a, ll b) {return a/gcd(a, b)*b;}
const int INF = 0x3f3f3f3f;
const int N = 4e4 + 10, Base = N/2;

int n, m;
int p[N];
unordered_map<int, int>s;

int get(int x)
{
    if(s.count(x) == 0) s[x] = ++n;
    return s[x];
}
int Find(int x)
{
    if(x != p[x])
    {
        p[x] = Find(p[x]);
    }
    return p[x];
}
int main()
{
    cin >> n >> m;
    n = 0;
    for(int i = 0; i <= N; i ++) p[i] = i;
    int res = m;
    for(int i = 1; i <= m; i ++)
    {
        int x, y;
        string type;
        cin >> x >> y >> type;
        x = get(x - 1), y = get(y);
        if(type == "even")
        {
            if(Find(x + Base) == Find(y))
            {
                res = i - 1;
                break;
            }
            p[Find(x)] = Find(y);
            p[Find(x + Base)] = Find(y + Base);
        }
        else
        {
            if(Find(x) == Find(y))
            {
                res = i - 1;
                break;
            }
            p[Find(x)] = Find(y + Base);
            p[Find(x + Base)] = Find(y);
        }
    }
    cout << res << endl;
    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值