7-10 卷图 (25分)
作为优秀大学的优秀学生,小w必然活在一个极度内卷的环境里:,每一次的大报告,每一次的期末考试,每一次的作业…
而在大x的某堂课上,在老师分完分组任务后,小w惊奇地发现班上内卷的同学自动分好组了。精通离散数学的小w将内卷的同学抽象成一个个的点,然后必不可能分到一个组的同学之间连出边,小w发现了这张图有如下性质:
图有n个结点,并且图上的n个节点可以分成A,B两个集合;
-
A∩B=∅
-
A集合内的点均无连边
-
B集合内的点均无连边
小w称这样的图为卷图,问题来了,小w自己突然有了很多奇奇怪怪的图,请你判断一下哪些是卷图,哪些不是卷图。
输入格式:
注意本题有多组数据。
每组测试数据第一行将读入一个数t(1≤t≤20),代表测试数据的组数。
之后的t组数据,每组数据的第一行读入两个数n,m(1≤n≤10^5 ,1≤m≤min(2n⋅(n−1) ,200000)),代表此图由n个点和m条边组成。
接下来m行,每行两个整数u,v(1≤u,v≤n),u≠v,代表点u,v之间连有一条边。
数据保证给出的图是简单图,即无重边与自环
输出格式:
对于每组样例的每张图,如果是卷图,输出Ye5!juantu
否则,输出N0!
输入样例:
在这里给出一组输入。例如:
2
3 3
1 2
2 3
3 1
3 2
1 2
1 3
输出样例:
在这里给出相应的输出。例如:
N0!
Ye5!juantu
提示
对于样例1,第一幅图为:
无法找到这样的两个点集A,B,故不是卷图
第二张图:
红色的点和蓝色的点构成题意所示的点集,故可以构成卷图。
解题
本题采用图的链式存储结构,输入的图不一定是一个连通图,因此要用并查集分别对每个集合的图进行bfs搜索,当图中存在奇数环时,表示不是二分图,bfs搜索时,对每个结点标记其bfs树层数,当遍历到没访问的结点时,访问并标记,当是已访问的结点时,说明有环了,而如果当前节点与已访问的结点的层数是相同的,说明是一个奇数环,就不是二分图。
通过画顶点较少的简单图的结构,观察,总结规律:
代码
#include <algorithm> //7-10 卷图 (25分)
#include <cstring>
#include <iostream>
#include <queue>
#include <vector>
using namespace std;
const int maxn = 1e5 + 2;
bool vis[maxn];
int Set[maxn];
//找出所有的环, 如果存在奇数顶点构成的环, 则不是卷图 (自己列举几个简单图)
/*复习一下指针的链式存储, C语言数据结构*/
struct AdjVNode {
//邻接链表的链
AdjVNode *Next;
int adjV;
};
typedef struct VNode {
AdjVNode *FirstEdge; //无权图
int k; //记录bfs树的层数
} AdjVList[maxn];
typedef struct GNode {
int Nv, Ne;
AdjVList L;
} * LGraph;
typedef struct ENode {
int v1, v2;
} * Edge;
void InsertEdge(LGraph G, Edge E) {
//给图插入边
AdjVNode *A;
A = new AdjVNode, A->adjV = E->v2;
A->Next = G->L[E->v1].FirstEdge, G->L[E->v1].FirstEdge = A;
A = new AdjVNode, A->adjV = E->v1;
A->Next = G->L[E->v2].FirstEdge, G->L[E->v2].FirstEdge = A;
}
LGraph CreateGraph(int Nv, int Ne) {
//新建图
LGraph G = new GNode();
G->Nv = Nv, G->Ne = Ne;
return G;
}
void DestroyGraph(LGraph G) {
//删除图, 释放内存
AdjVNode *A;
for (int i = 1; i <= G->Nv; i++)
while (G->L[i].FirstEdge) {
A = G->L[i].FirstEdge;
G->L[i].FirstEdge = A->Next;
delete A;
}
delete G;
G = NULL;
}
bool bfs(LGraph G, int start) {
queue<int> q;
q.push(start);
G->L[start].k = 1;
vis[start] = true;
AdjVNode *A;
while (!q.empty()) {
int v = q.front(); //当前节点
int k = G->L[v].k;
q.pop();
A = G->L[v].FirstEdge;
while (A) {
if (!vis[A->adjV]) {
//如果访问到新节点, 入队
vis[A->adjV] = true;
q.push(A->adjV);
G->L[A->adjV].k = k + 1;
} else if (k == G->L[A->adjV].k) {
//如果是已访问的结点, 说明找到环路了,而如果是同一层说明是奇数环
return false;
}
A = A->Next;
}
}
return true;
}
/*并查集*/
int FindSet(int x) {
if (Set[x] < 0) return x;
return Set[x] = FindSet(Set[x]);
}
void Union(int r1, int r2) {
if (r1 == r2) return;
if (Set[r1] < Set[r2])
Set[r1] += Set[r2], Set[r2] = r1;
else
Set[r2] += Set[r1], Set[r1] = r2;
}
int main() {
int T, n, m;
cin >> T;
LGraph G = NULL;
vector<int> vv;
while (T--) {
memset(vis, false, sizeof(vis));
memset(Set, -1, sizeof(Set));
vv.clear();
scanf("%d %d", &n, &m);
G = CreateGraph(n, m);
Edge E = new ENode();
for (int i = 0; i < m; i++) {
scanf("%d %d", &E->v1, &E->v2);
Union(FindSet(E->v1), FindSet(E->v2));
InsertEdge(G, E);
}
/*求解判断*/
for (int i = 1; i <= n; i++) //将所有集合的根加入数组记录
if (Set[i] < 0) vv.push_back(i);
bool bo = true;
for (int i = 0; i < vv.size(); i++) {
int start = vv[i];
bo = bfs(G, start);
if (!bo) {
printf("N0!\n");
break;
} else if (i == vv.size() - 1) //如果bo = true, 且是最后一个
printf("Ye5!juantu\n");
}
delete E;
DestroyGraph(G);
}
system("pause");
return 0;
}