其他分享
首页 > 其他分享> > 奇偶游戏(二分图染色法/带权并查集/扩展域并查集)

奇偶游戏(二分图染色法/带权并查集/扩展域并查集)

作者:互联网

题目链接:https://www.acwing.com/problem/content/241/

题目描述

简要题解

一.二分图染色法

首先,我们需要将题意所维护的信息转化一下,对于区间信息来说一定是不容易维护的,因此我们尝试转化为
端点维护,可以发现,若[l,r]中1的个数为偶数,则等同于[1,l-1]和[1,r]的1的个数同奇偶。因此我们有如
下转换:

  1. [l,r]中1的个数为偶数:[1,l-1],[1,r]同奇偶
  2. [l,r]中1的个数为奇数:[1,l-1],[1,r]不同奇偶

对于每个点,此时有奇和偶两种状态,此时的题相当于,需要将所有点分成两列,并且同一列内部是同奇偶性,不同列之间是不同奇偶。看什么时候会出现矛盾。因此我们可以用二分图判定来解决。因此思路为:

  1. 因为N为1e9,首先进行离散化
  2. 建无向图,若l-1和r同奇偶,设边权为0。若l-1和r不同奇偶,设边权为1。
  3. 二分跑logn次染色确定最早出现矛盾的地方。因为染色法不是通过给定顺序,而是通过边dfs来遍历的,即使设定上序号,也不好判断出第一个出现矛盾的序号。

题解代码

//二分图染色法
#include <bits/stdc++.h>
using namespace std;
const int N=20010;
vector<int>v;
int h[N],e[N],ne[N],idx,w[N];
int color[N],n;
int find(int x)
{
    return lower_bound(v.begin(),v.end(),x)-v.begin()+1;
}
struct node
{
    int l,r,x;
}sz[N];
void add(int a,int b,int c)
{
    e[idx]=b,w[idx]=c,ne[idx]=h[a],h[a]=idx++;
}
bool dfs(int u,int c)
{
    color[u]=c;
    for(int i=h[u];i!=-1;i=ne[i])
    {
        int j=e[i];
        if(!color[j])
        {
            if(w[i]==1)
            {
                if(!dfs(j,3-c))return false;
            }
            else if(!dfs(j,c))return false;
        }
        else
        {
            if(w[i]==1)
            {
                if(color[u]==color[j])return false;
            }
            else
            {
                if(color[u]!=color[j])return false;
            }
        }
    }
    return true;
}
bool check(int x)
{
    memset(h,-1,sizeof h);
    memset(color,0,sizeof color);
    idx=0;
    for(int i=1;i<=x;i++)
    {
        add(sz[i].l,sz[i].r,sz[i].x);
        add(sz[i].r,sz[i].l,sz[i].x);
    }
    int flag=0;
    for(int i=1;i<=v.size();i++)
    {
        if(!color[i])
        {
            if(!dfs(i,1))return false;
        }
    }
    return true;
}
int main(void)
{
    cin>>n;
    cin>>n;
    for(int i=1;i<=n;i++)
    {
        int a,b;
        string str;
        cin>>a>>b>>str;
        v.push_back(a);
        v.push_back(a-1);
        v.push_back(b);
        sz[i]={a,b,str[0]=='e'?0:1};
    }
    sort(v.begin(),v.end());
    v.erase(unique(v.begin(),v.end()),v.end());
    for(int i=1;i<=n;i++)
    {
        sz[i].l=find(sz[i].l-1);
        sz[i].r=find(sz[i].r);
    }
    int l=0,r=n;
    while(l<r)
    {
        int mid=l+r+1>>1;
        if(check(mid))l=mid;
        else r=mid-1;
    }
    cout<<l<<endl;
    system("pause");
    return 0;
}

二.带权并查集

  对于此题,我们也可以用带权并查集来解决,带权并查集的核心就是通过维护x,y到根的相对位置,来确定x,y之间的关系。 同样的,先将题目转化成维护l-1和r两个端点信息。顺序遍历所有的语句。此时有如下递推式:

题解代码

//带权并查集
#include <bits/stdc++.h>
using namespace std;
const int N=20010;
vector<int>v;
int h[N],e[N],ne[N],idx,d[N];
int p[N*4],n;
int f(int x)
{
    return lower_bound(v.begin(),v.end(),x)-v.begin()+1;
}
struct node
{
    int l,r,x;
}sz[N];
int find(int a)
{
    if(p[a]!=a)
    {
        int t=p[a];
        p[a]=find(p[a]);
        d[a]^=d[t];
    }
    return p[a];
}
int main(void)
{
    cin>>n;
    cin>>n;
    for(int i=1;i<=4*n;i++)p[i]=i;
    for(int i=1;i<=n;i++)
    {
        int a,b;
        string str;
        cin>>a>>b>>str;
        v.push_back(a);
        v.push_back(a-1);
        v.push_back(b);
        sz[i]={a,b,str[0]=='e'?0:1};
    }
    sort(v.begin(),v.end());
    v.erase(unique(v.begin(),v.end()),v.end());
    for(int i=1;i<=n;i++)
    {
        sz[i].l=f(sz[i].l-1);
        sz[i].r=f(sz[i].r);
    }
    int flag=0;
    for(int i=1;i<=n;i++)
    {
        int x=sz[i].l,y=sz[i].r,z=sz[i].x;
        int pa=find(x),pb=find(y);
        if(!z)
        {
            if(pa==pb)
            {
                if(d[x]^d[y])
                {
                    cout<<i-1<<endl;
                    flag=1;
                    break;
                }
            }
            else
            {
                d[pa]=d[x]^d[y];
                p[pa]=pb;
            }
        }
        else
        {
            if(pa==pb)
            {
                if(d[x]^d[y]==0)
                {
                    cout<<i-1<<endl;
                    flag=1;
                    break;
                }
            }
            else
            {
                d[pa]=d[x]^d[y]^1;
                p[pa]=pb;
            }
        }
    }
    if(!flag)cout<<n<<endl;
    system("pause");
    return 0;
}

三.扩展域并查集

  扩展域并查集解决此题相对于前两种更加好理解,扩展域并查集对于条件来划分集合,引申出x和x+n点,此处x点表示为x是奇数, x+n表示x为偶数,因此我们可以开始讨论:

题解代码

//扩展域并查集
#include <bits/stdc++.h>
using namespace std;
const int N=20010;
vector<int>v;
int h[N],e[N],ne[N],idx,d[N];
int p[N*4],n;
int f(int x)
{
    return lower_bound(v.begin(),v.end(),x)-v.begin()+1;
}
struct node
{
    int l,r,x;
}sz[N];
int find(int a)
{
    if(p[a]!=a)
    {
        int t=p[a];
        p[a]=find(p[a]);
        d[a]^=d[t];
    }
    return p[a];
}
int main(void)
{
    cin>>n;
    cin>>n;
    for(int i=1;i<=4*n;i++)p[i]=i;
    for(int i=1;i<=n;i++)
    {
        int a,b;
        string str;
        cin>>a>>b>>str;
        v.push_back(a-1);
        v.push_back(b);
        sz[i]={a,b,str[0]=='e'?0:1};
    }
    sort(v.begin(),v.end());
    v.erase(unique(v.begin(),v.end()),v.end());
    for(int i=1;i<=n;i++)
    {
        sz[i].l=f(sz[i].l-1);
        sz[i].r=f(sz[i].r);
    }
    int flag=0;
    for(int i=1;i<=n;i++)
    {
        int x=sz[i].l,y=sz[i].r,z=sz[i].x;
        int pa1=find(x),pa2=find(x+n);
        int pb1=find(y),pb2=find(y+n);
        if(!z)
        {
            if(pa1==pb2)
            {
                cout<<i-1;
                flag=1;
                break;
            }
            p[pa1]=pb1;
            p[pa2]=pb2;
        }
        else
        {
            if(pa1==pb1)
            {
                cout<<i-1;
                flag=1;
                break;
            }
            p[pa1]=pb2;
            p[pa2]=pb1;
        }
    }
    if(!flag)cout<<n<<endl;
    system("pause");
    return 0;
}

标签:奇偶,begin,return,sz,int,查集,带权,end,find
来源: https://blog.csdn.net/dylolorz/article/details/118462675