题目链接: 传送门
题目大意: 有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();
}
}