【Codeforces】Pinely Round 1 (Div. 1 + Div. 2) E. Make It Connected | 图论、分类讨论

210 阅读4分钟

本文已参与「新人创作礼」活动, 一起开启掘金创作之路。

【Codeforces】Pinely Round 1 (Div. 1 + Div. 2) E. Make It Connected | 图论、分类讨论

题目链接

Problem - E - Codeforces

题目

image.png

题目大意

以邻接矩阵的形式出包含 nn 个点的无向图,保证没有自环和重边(

可以对这个图执行若干次以下操作:

  • 选择图中的一个点 xx,对于图中任意不是 xx 本身的点 vv:如果 xxvv 之间有边相连,删除这条边;否则在 xxvv 之间建一条边。

求最小的操作次数使得整张图变成连通图。并输出操作次数最小的任一合法方案。

思路

答案是 0

首先判断整张图是否最初就是连通图,如果是直接输出 0 即可。

答案是 1

如果我们选择了点 xx 执行一次操作,就可以把整张图变成连通图,就称 xx特殊点

我们把整张图划分成若干个块,块内节点相互连通,块与块之间不能连通。如果一个连通块的大小为 1,即如果整张图中包含一个孤立点,显然它是特殊节点。

在排除这种情况后,图中只包括节点个数大于 1 的连通块,此时特殊点存在且只存在于不是完全子图的连通块中,我们下面对该结论进行说明:

  • 特殊点不可能存在于完全图中:对于完全图中的任意一个节点,进行操作后它一定与其原本所在的连通块失去联系。
  • 特殊点一定存在于不是完全子图的连通块中:非完全子图中度最小的点就是特殊点。

为什么说非完全子图中度最小的点就是特殊点呢?

假设我们选择的非完全子图中度最小的点是 xx,经过上述筛选后 xx 不是孤立点,所以一定存在与其相连的点。我们将点 xx 从图中删除,则会产生若干个连通块。假设其中存在连通块 1 只包括与 xx 直接相连的点,如下图所示:

image.png

容易发现:

  • 如果连通块 1 不是产生的唯一一个连通块,那么连通块 1 中的点的度一定小于点 xx
  • 否则,只有在连通块 1 是完全图的情况下,连通块 1 中度最小的节点的度才会和节点 xx 的度相等。而此时操作前 xx 与连通块 1 的并集将会是完全图,与假设不符。

综上我们可得到非完全子图中度最小的点就是特殊点。所以将与点 xx 相邻的边从图中删除后,产生的每个连通块中都包括与 xx 不直接相邻的点,在建边环节这些连通块将会与 xx 重新形成连通块。

这就说明了只要原图中存在孤立点或者不是完全子图的连通块,就可以使用 1 次操作达成目标。

答案是 2

如果原图划分出的连通块大小均不小于 22 且均为完全图,如果划分出的连通块数量大于 2,我们显然可以随便对一个节点 xx 进行操作,然后将会有至少两个完全图被合并成一个连通块,显然合并出的连通块不是完全图,再进行一次操作即可。

答案是连通块大小

最后我们只剩下原图划分为两个完全图的情况了,此时我们每次操作后仍会产生两个完全图,并且让一个完全图的大小增加 1,另一个减小 1。所以此时答案是点数较少的完全子图的大小。

代码

#include <iostream>
#include <algorithm>
#include <math.h>
#include <stdio.h>
#include <map>
#include <set>
#include <queue>
using namespace std;
using LL=long long;
const int N=4005;
int n,a[N][N];
int d[N],f[N],id=0,minn,cnt=0;
int find(int x)
{
	if (f[x]==x) return x;
	return f[x]=find(f[x]);
}
char ch;
int get01()
{
	for (ch=getchar();ch!='0'&&ch!='1';ch=getchar());
	return ch-'0';
}
LL solve()
{
	scanf("%d",&n);
	for (int i=1;i<=n;++i) f[i]=i,d[i]=0;
	cnt=0;
	for (int i=1;i<=n;++i)
		for (int j=1;j<=n;++j)
		{
			a[i][j]=get01();
			if (a[i][j]&&find(i)!=find(j))
			{
				f[f[i]]=f[j];
				cnt++;
			}
			d[j]+=a[i][j];
		}
	cnt=n-cnt;
	if (cnt==1) return printf("0\n"),0;
	
	minn=1;
	for (int i=1;i<=n;++i)
	{
		find(i);
		if (d[i]<d[minn]) minn=i;
	}
	if (d[minn]==0) return printf("1\n%d\n",minn),0;
	
	id=0;
	for (int i=1;i<=n&&!id;++i)
		for (int j=i+1;j<=n&&!id;++j)
			if (f[i]==f[j]&&a[i][j]==0) id=f[i];
	if (id!=0)
	{
		minn=id;
		for (int i=1;i<=n;++i)
			if (f[i]==id&&d[i]<d[minn]) minn=i;
		printf("1\n%d\n",minn);
		return 0;
	}
	
	if (cnt>2)
	{
		printf("%d\n%d ",2,1);
		id=f[1];
		minn=2;
		for (int i=2;i<=n;++i)
		{
			if (a[i][1]) d[i]--;
			else d[i]++;
			a[i][1]=a[1][i]=!a[i][1];
			if (f[i]!=id&&d[minn]>d[i]) minn=i;
		}
		printf("%d\n",minn); 
		return 0;
	}
	id=f[1];
	cnt=0;
	for (int i=1;i<=n;++i)
		if (f[i]==id) cnt++;
	printf("%d\n",min(cnt,n-cnt));
	if (cnt<=n-cnt) cnt=0;
	else cnt=1;
	
	for (int i=1;i<=n;++i)
		if ((f[i]==id)^cnt) printf("%d ",i);
	printf("\n");
	return 0;		
}
int main()
{
	int T=1;
	scanf("%d",&T);
	while (T--) solve();
	return 0;
}