Luogu P10179 水影若深蓝 题解 [ 绿 ] [ 并查集 ] [ 构造 ]

水影若深蓝:挺好的一道并查集构造题。

观察

不难发现“距离为 2”这个条件我们可以通过黑白染色实现,我们把他们的中转点染成与他们相反的颜色,把这两个距离为 2 的点染成相同颜色。

这个染色问题就很并查集。

于是我们用并查集维护相同的种类。

显然,当图上只有一个连通块的时候,无解;否则我们一定可以找到一组解,因为一棵树一定可以进行黑白染色。

朴素想法

注意到每次连边只能连两个颜色不同的点的边,并且还要满足某些点之间距离为 2 的限制,我们考虑一个暴力连边。

每次用并查集合并完颜色后,我们重新遍历一遍那些限制,然后扫一遍所有点,找到一个与他们颜色不同的点,分别向他们连一条边。

然后剩下的点因为没有限制,所以瞎几把乱连就好,这个和 MST 一样的实现方式。

但这样是 O(n2) 的,过不了。

优化

因为同一个连通块里的点可以找同一个中转点,所以我们可以构造一个菊花,先钦定一个颜色不同的点(不在同一个连通块内),然后把某连通块所有的点与它连边,就可以让他们之间的距离全部为 2

构造

进一步优化,可以发现我们只要找到两个不同集合里的点就好了,不用对于每个连通块一个一个找中转点。

我们选择两个处于不同连通块的祖先 p1,p2,然后对于祖先非 p1 的点,我们连一条向 p1 的边;否则连 p2

最后注意 p1 不能连任何边即可。

代码

#include <bits/stdc++.h>
#define fi first
#define se second
#define lc (p<<1)
#define rc ((p<<1)|1)
using namespace std;
typedef long long ll;
typedef pair<int,int> pi;
int f[300005],n,m,a[300005],b[300005];
void init()
{
	for(int i=1;i<=n;i++)f[i]=i;
}
int findf(int x)
{
	if(f[x]!=x)f[x]=findf(f[x]);
	return f[x];
}
void combine(int x,int y)
{
	int fx=findf(x),fy=findf(y);
	f[fx]=fy;
}
void solve()
{
	cin>>n>>m;
	init();
	for(int i=1;i<=m;i++)
	{
		cin>>a[i]>>b[i];
		combine(a[i],b[i]);
	}
	bool ilg=1;
	for(int i=1;i<=n;i++)
	{
		if(i>1)ilg=(ilg&(findf(i)==findf(i-1)));
	}
	if(ilg==1)
	{
		cout<<"No"<<endl;
		return;
	}
	cout<<"Yes"<<endl;
	int p1=-1,p2=-1;
	for(int i=1;i<=n;i++)
	{
		int fa=findf(i);
		if(p1==-1)p1=fa;
		else if(p2==-1&&fa!=p1)p2=fa;
	}
	for(int i=1;i<=n;i++)
	{
		int fa=findf(i);
		if(i==p1)continue;
		if(fa==p1)cout<<i<<" "<<p2<<endl;
		else cout<<i<<" "<<p1<<endl;
	}
	cout<<endl;
}
int main()
{
	ios::sync_with_stdio(0);
	cin.tie(0);
	cout.tie(0);
	int t;
	cin>>t;
	while(t--)solve();
	return 0;
}
posted @   KS_Fszha  阅读(4)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 微软正式发布.NET 10 Preview 1:开启下一代开发框架新篇章
· 没有源码,如何修改代码逻辑?
· PowerShell开发游戏 · 打蜜蜂
· 在鹅厂做java开发是什么体验
· WPF到Web的无缝过渡:英雄联盟客户端的OpenSilver迁移实战
点击右上角即可分享
微信分享提示