一.题意
给你n个双面骨牌,每一面都有一个数字(这个数字在1~n范围内),你的目标是将这些骨牌分成两堆,使得每一堆每张骨牌每一面的数字没有相同的,也就是每一堆中不包含相同的数字。
二.所用工具
并查集
三.思路
如果可以分成两堆那么一定具有以下性质:
1.每张骨牌正反面不同。
2.每个数字一定出现2次(假设某个数字出现少于2次,由于一共有2*n个数字(数字范围在1~n之间),那么一定会有其他数字出现多于2次,那么一定不能分成两堆使得这个数字在两堆出现且仅出现一次)
3.如果把同时在一张骨牌中出现的数字都连一条边,那么最终会形成由一个或多个环(无向图意义上的环)。
(成环原因:因为每个数字都会出现两次,因此每个数字度数都是2,因此满足环的性质)
比如
1 2
2 1
3 4
4 3
最后会形成一个 1-2(中间双向边) 环和一个3-4 (中间双向边)环
再比如
1 2
2 3
3 1
最后会形成一个1-2-3-1环
4.如果能分两堆,那么构成的所有环(每个连通块)中,都是偶数环(边数为偶数条,里面含有的数字种类也是偶数个)。
4的证明:首先再强调一遍图中有边的俩数的话就是有由这俩数构成的骨牌。
类似染色,想分成两组的话我们在环上间隔取对数
间隔就是相邻两个数放到一个堆里面,下两个放在不同堆里面,不明白怎么取看下面模拟
(若不这样间隔一堆取完再换下一堆这样,一定会在一堆中出现相同数字 例如 1-2-3-1环 不间隔取的话那么从左往右第一次取1-2放第一堆 第二次取2-3放第一堆 2会重复)
下面还有具体分类取数过程,文字描述结束还有图。
eg
大家可以画一个这样的奇数环,然后用方框和圆环分别代表1组和2组,在环上画框。
下面有图,可以文字介绍结合图来看哦!
奇数(边数为奇数,该环中的数字种类也是奇数)环 1-2-3-1
放第一堆 1-2
放第二堆 2-3
放第一堆 3-1
最后一定会重复,因为最终1组圈住2-3的方框和1组圈住4-2的方框有了交集。
但是偶数(边数为偶数,该环中的数字种类也是偶数)环不会
例如:1-2-3-4-1
放第一堆 1-2
放第二堆 2-3
放第一堆 3-4
放第二堆 4-1
这样就全部圈完啦!并且⚪和⚪没有交集,□和□也没有交集,也就是不会在同一堆中出现相同数字!
左为奇数环例子,右为偶数环例子
那我们该如何实现呢?
这样每个牌俩数连边构成的一个或多个环,每一个环其实就是一个连通块,那么我们刚刚看出来了当一个连通块中数字种类是奇数的话就是不行,是偶数的话就是行,因此我们可以用带大小的并查集,因为并查集每个连通块只能每种数字存一个是吧,因此并查集每个连通块的大小为偶数(就是一个环中有偶数种数)就能分,至少有一个连通块数字个数是奇数(就是一个环中有奇数种数)就不能分。
四.代码实现
#include <iostream>
#include <cstring>
#include <algorithm>
#include <map>
using namespace std;
const int N = 2e5+10;
map<int,int> cnt,cntab;
int f[N];
int find(int a)
{
if(a!=f[a]) f[a]=find(f[a]);
return f[a];
}
void merge(int a,int b)
{
int fa=find(a);
int fb=find(b);
if(fa!=fb)
{
f[fb]=fa;
cnt[fa]+=cnt[fb];
}
}
void solve()
{
int n;
cin>>n;
cnt.clear(),cntab.clear();
for(int i=1;i<=n;i++) f[i]=i,cnt[i]=1;
for(int i=1;i<=n;i++)
{
int a,b;
cin>>a>>b;
merge(a,b);
cntab[a]++,cntab[b]++;
}
for(int i=1;i<=n;i++)
{
if(cntab[i]!=2)//条件2
{
puts("NO");
return;
}
if(f[i]==i)
{
if(cnt[i]&1)//条件4
{
puts("NO");
return;
}
}
}
puts("YES");
}
int main()
{
int T;
cin>>T;
while(T--)
{
solve();
}
}