E. Split Into Two Sets(种类并查集+染色法判二分图)

题目链接: 传送门

 

 题目大意: 有n组多米诺骨牌, 每组牌上有两个数字a,b,(1<=a,b<=n), 问是否能将这n组骨牌分成两组, 使得每组骨牌上的数字各不一样, 且所有骨牌能分到(即一组的所有骨牌的数字为1~n).

思路: 这题有多种做法, 这里介绍两种, 一种是用种类并查集做, 一种是用染色法来进行二分图构图

一、种类并查集

基础的并查集维护的是一个关系, 如朋友的朋友是朋友, 而种类并查集可以维护多个关系, 如敌人的敌人是朋友, 多了个敌对这一关系, 一般可以通过扩展并查集规模实现;

如: 1 2 3 4, 四个人,  1,2 2,3 是朋友, 那么通过并查集连接可知1,3是朋友

若要加上敌人关系, 我们可以扩展一倍并查集的规模, 则为 1 2 3 4 1+4 2+4 3+4 4+4

即 前四个1 2 3 4存朋友关系, 后四个5 6 7 8为敌人关系, 那么若 1,2为敌对关系, 就连1,2+4 和 1+4, 2两条边, 来建立出敌对关系, 这样, 当2,3为敌对关系时候, 就会发现1,3被间接连接起来, 即敌人的敌人是朋友, 就完成了两个种类的并查集

 

 那么对于此题我们可以建立两个种类并查集, 一个种类为一组来进行并查集构建, 如样例5

2 1
1 2
4 3
4 3
5 6
5 7
8 6
7 8

每次将一个骨牌放入一个集合, 如 1,2放入集合1,  1+n, 2+n则放入集合2, 这样在第二次出现1,2时候 就可以判断, 1,2重复出现在同一个集合

代码如下:

#include <iostream>
#include <cstring>
#include <cstdio>
#include <string>
#include <iterator>
#include <unordered_set>
#include <cmath>
#include <algorithm>
#include <numeric>
#include <sstream>
#include <map>
#include <stack>
#include <set>
#include <queue>
#include <iomanip>
#include <bitset>
#include <unordered_map>
using namespace std;

stringstream ss;
#define endl "\n"
typedef long long ll;
typedef pair<int, int> PII;
typedef pair<pair<int, int>, int> PIII;
const int N = 3e5+10, M = 20, mod = 1e9+7;
const int INF = 0x3f3f3f3f;

int n,q;
int p[2*N]; // 扩展一倍并查集规模

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

void merge(int x,int y)
{
    int px = find(x);
    int py = find(y);
    if(px != py) p[px] = py;
}

void solve()
{
    cin>>n;
    map<int, int> mp; // 记数字次数
    for(int i = 1; i<=2*n; i++) p[i] = i;
    int f = 1;
    for(int i = 1; i<=n; i++)
    {
        int a,b;
        cin>>a>>b;
        mp[a]++, mp[b]++;
        if(mp[a]>2 || mp[b]>2) f = 0;
        if(a == b) f = 0;
        int x = a, y = b, dx = a+n, dy = b+n; // x,y在一个集合, dx,dy在另一个集合
        if(find(x) == find(y)) f = 0;
        else
        {
            merge(x, dy);
            merge(y, dx);
        }
    }

    if(f) cout<<"YES"<<endl;
    else cout<<"NO"<<endl;
}

int main()
{
    ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
//    solve();
    int T;
    cin>>T;
    while(T -- )
    {
        solve();
    }
}

二、二分图

将每一个骨牌看做双向边连接, 如样例5

2 1
1 2
4 3
4 3
5 6
5 7
8 6
7 8

连完后建成的图为 {1,2}-{2,1}, {3,4}-{3,4}, {5,6}-{6,8}-{8,7}-{7,5}三个环 不难发现当环是偶数环时候, 我们可以隔着取放在一个集合里面, 如 {5,6}-{6,8}-{8,7}-{7,5}, 取{5,6},{8,7}放在一个集合, 其他两个放在另一个集合(即构建出一个二分图), 这样取两个集合中是不会出现重复的, 而若出现奇数环时, 取牌必定会有重复, 所以建完图后, 判断是否存在奇数环即可.

代码如下: 

#include <iostream>
#include <cstring>
#include <cstdio>
#include <string>
#include <iterator>
#include <unordered_set>
#include <cmath>
#include <algorithm>
#include <numeric>
#include <sstream>
#include <map>
#include <stack>
#include <set>
#include <queue>
#include <iomanip>
#include <bitset>
#include <unordered_map>
using namespace std;

stringstream ss;
#define endl "\n"
typedef long long ll;
typedef pair<int, int> PII;
typedef pair<pair<int, int>, int> PIII;
const int N = 3e5+10, M = 20, mod = 1e9+7;
const int INF = 0x3f3f3f3f;

int n,q;
int col[N];
vector<int> v[N];
int ok = 1;

void dfs(int u)
{
    for(auto t : v[u])
    {
        if(!col[t])
        {

            col[t] = -col[u];
            dfs(t);
        }else if(col[t] == col[u]) ok = 0;
    }
}

void solve()
{
    cin>>n;
    map<int, int> mp; // 记数字次数
    for(int i = 1; i<=n; i++) v[i].clear(), col[i] = 0;
    int f = 1;
    for(int i = 1; i<=n; i++)
    {
        int a,b;
        cin>>a>>b;
        mp[a]++, mp[b]++;
        if(mp[a]>2 || mp[b]>2) f = 0;
        if(a == b) f = 0;
        v[a].push_back(b);
        v[b].push_back(a);
    }
    if(!f)
    {
        cout<<"NO"<<endl;
        return;
    }
    ok = 1;
    for(int i = 1; i<=n; i++)
    {
        if(!col[i]) col[i] = 1, dfs(i); // 染色法判断奇偶
        if(!ok)
        {
            cout<<"NO"<<endl;
            return;
        }
    }
    cout<<"YES"<<endl;
}

int main()
{
    ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
//    solve();
    int T;
    cin>>T;
    while(T -- )
    {
        solve();
    }
}

  • 9
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值