本文已参与「新人创作礼」活动, 一起开启掘金创作之路。
【Codeforces】Pinely Round 1 (Div. 1 + Div. 2) E. Make It Connected | 图论、分类讨论
题目链接
题目
题目大意
以邻接矩阵的形式出包含 个点的无向图,保证没有自环和重边(
可以对这个图执行若干次以下操作:
- 选择图中的一个点 ,对于图中任意不是 本身的点 :如果 和 之间有边相连,删除这条边;否则在 和 之间建一条边。
求最小的操作次数使得整张图变成连通图。并输出操作次数最小的任一合法方案。
思路
答案是 0
首先判断整张图是否最初就是连通图,如果是直接输出 0 即可。
答案是 1
如果我们选择了点 执行一次操作,就可以把整张图变成连通图,就称 为特殊点。
我们把整张图划分成若干个块,块内节点相互连通,块与块之间不能连通。如果一个连通块的大小为 1,即如果整张图中包含一个孤立点,显然它是特殊节点。
在排除这种情况后,图中只包括节点个数大于 1 的连通块,此时特殊点存在且只存在于不是完全子图的连通块中,我们下面对该结论进行说明:
- 特殊点不可能存在于完全图中:对于完全图中的任意一个节点,进行操作后它一定与其原本所在的连通块失去联系。
- 特殊点一定存在于不是完全子图的连通块中:非完全子图中度最小的点就是特殊点。
为什么说非完全子图中度最小的点就是特殊点呢?
假设我们选择的非完全子图中度最小的点是 ,经过上述筛选后 不是孤立点,所以一定存在与其相连的点。我们将点 从图中删除,则会产生若干个连通块。假设其中存在连通块 1 只包括与 直接相连的点,如下图所示:
容易发现:
- 如果连通块 1 不是产生的唯一一个连通块,那么连通块 1 中的点的度一定小于点 。
- 否则,只有在连通块 1 是完全图的情况下,连通块 1 中度最小的节点的度才会和节点 的度相等。而此时操作前 与连通块 1 的并集将会是完全图,与假设不符。
综上我们可得到非完全子图中度最小的点就是特殊点。所以将与点 相邻的边从图中删除后,产生的每个连通块中都包括与 不直接相邻的点,在建边环节这些连通块将会与 重新形成连通块。
这就说明了只要原图中存在孤立点或者不是完全子图的连通块,就可以使用 1 次操作达成目标。
答案是 2
如果原图划分出的连通块大小均不小于 且均为完全图,如果划分出的连通块数量大于 2,我们显然可以随便对一个节点 进行操作,然后将会有至少两个完全图被合并成一个连通块,显然合并出的连通块不是完全图,再进行一次操作即可。
答案是连通块大小
最后我们只剩下原图划分为两个完全图的情况了,此时我们每次操作后仍会产生两个完全图,并且让一个完全图的大小增加 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;
}