题目描述
难度分:2400
输入T(≤800)表示T组数据。所有数据的n之和≤4000。
每组数据输入n(2≤n≤4000)和一个n×n的01
矩阵g。
g表示一个无向图的邻接矩阵,其中g[i][j]=1表示有一条边连接i和j。
保证g[i][j]=g[j][i]且g[i][i]=0。
有如下操作:
- 选择一个点x,对于其余每个点y,如果x和y有边,则删除,如果没有边,则添加一条连接x和y的边。
要使图是连通的,最少要操作多少次?输出操作次数和每次操作的x。顶点编号从1开始。
输入样例
4
3
011
100
100
3
000
001
010
4
0100
1000
0001
0010
6
001100
000011
100100
101000
010001
010010
输出样例
0
1
1
2
3 4
3
2 5 6
算法
分类讨论
很难,感觉应该属于结论题那一类题目,第一次见的话都不知道怎么讨论才能不重不漏地考虑所有情况。
按照如下顺序,依次判断:
- 整个图只有一个连通块,那么无需操作,输出0。
- 图中存在一个孤立点,那么只需对这个点操作,即可让整个图连通。
- 只要图中存在一个不是团(完全子图)的连通块,那么只需对这个连通块的度最小的点操作。证明如下。
- 图至少3个连通块(经过了3的过滤,此时所有连通块都是完全子图),那么只需操作两次,随便选两个在不同连通块的点。
- 图只有两个连通块(经过了3的过滤,此时所有连通块都是完全子图),那么选其中点数少的连通块操作,操作次数就是该连通块的点数。
3的证明:
设这个度最小的点是x,设x所在连通分量为V。由于V不是团,所以V中的某些点在操作前不与x相连,在操作后会与x相连。
如果x不是割点,那么操作后,V不会分成更多的连通块,并且仍然有点连着x,所以整个图是连通的。
如果x是割点(例如两个三角形各有一个顶点连着x),首先来说明几个性质:
- 性质1:由于x的度数至少是2(割点性质),所以其余每个点的度数也至少是2,这意味着V中没有叶子。
- 性质2:删除x会把V分成若干个连通块,每个连通块至少有3个点(根据性质1)。
现在的问题是:操作后,每个连通块是否都有点与x相连?
反证法:假设有一个连通块U没有点与x相连,设这个连通块的大小为|U|。由于操作前x除了与这|U|个点相连外,还与其它点相连(注意我们假设x是割点),这说明x的度数至少是|U|+1,但由于x的度数最小,所以|U|中必定有度数至少为|U|+1的点,但U中只有|U|个点,度数不可能超过 |U|,矛盾。所以操作后,每个连通块都至少有一个点与x相连。
复杂度分析
时间复杂度
遍历完一遍图之后就可以O(1)进行分类讨论,输出每类结果的答案,因此算法的时间复杂度为O(n)。
空间复杂度
除去题目输入的邻接矩阵g。遍历图的时候需要判重数组st,防止重复经过同一个节点,规模为O(n);节点的度数组deg,规模为O(n);每个连通分量的节点集合groups,在最差情况下需要存满n个节点,规模为O(n)。所以算法整体的额外空间复杂度为O(n)。
C++ 代码
#include <iostream>
#include <cstring>
#include <algorithm>
#include <vector>
using namespace std;
const int N = 4010;
int t, n, deg[N];
char s[N][N];
bool st[N];
void dfs(int u, vector<int>& vs) {
st[u] = true;
vs.push_back(u);
for(int v = 1; v <= n; v++) {
if(!st[v] && s[u][v] == '1') {
dfs(v, vs);
}
}
}
int main() {
scanf("%d", &t);
while(t--) {
scanf("%d", &n);
for(int i = 1; i <= n; i++) {
deg[i] = st[i] = 0;
}
for(int i = 1; i <= n; i++) {
scanf("%s", s[i] + 1);
for(int j = 1; j <= n; j++) {
deg[i] += s[i][j] == '1'? 1: 0;
}
}
vector<vector<int>> groups;
bool flag = false;
for(int i = 1; i <= n; i++) {
if(st[i]) continue;
vector<int> group;
dfs(i, group);
int m = group.size();
if(m == n) {
// 只有一个连通块
puts("0");
flag = true;
break;
}
if(m == 1) {
// 有一个孤立点
puts("1");
printf("%d\n", group[0]);
flag = true;
break;
}
int mn = group[0];
for(int x: group) {
if(deg[x] < deg[mn]) {
mn = x;
}
}
if(deg[mn] < m - 1) {
// 有一个节点的度不是m-1,当前连通块不是完全图
puts("1");
printf("%d\n", mn);
flag = true;
break;
}
groups.push_back(group);
}
if(!flag) {
if(groups.size() > 2) {
puts("2");
printf("%d %d\n", groups[0][0], groups[1][0]);
}else {
if(groups[0].size() < groups[1].size()) {
printf("%d\n", (int)groups[0].size());
for(int x: groups[0]) {
printf("%d ", x);
}
}else {
printf("%d\n", (int)groups[1].size());
for(int x: groups[1]) {
printf("%d ", x);
}
}
puts("");
}
}
}
return 0;
}