描述
判断一个图是否能够用一笔画下来.规定,所有的边都只能画一次,不能重复画。
输入
第一行只有一个正整数N(N<=10)表示测试数据的组数。
每组测试数据的第一行有两个正整数P,Q(P<=1000,Q<=2000),分别表示这个画中有多少个顶点和多少条连线。(点的编号从1到P)
随后的Q行,每行有两个正整数A,B(0<A,B<P),表示编号为A和B的两点之间有连线。
输出
如果存在符合条件的连线,则输出"Yes",
如果不存在符合条件的连线,输出"No"。
样例输入
2
4 3
1 2
1 3
1 4
4 5
1 2
2 3
1 3
1 4
3 4
样例输出
No
Yes
思路:
1.先判断图是否为连通图,不连通则一笔无法画出。
2.若为连通图,根据欧拉回路判断即可。
1、基本概念:
(1)定义
欧拉通路 (欧拉迹)—通过图中每条边一次且仅一次,并且过每一顶点的通路。
欧拉回路 (欧拉闭迹)—通过图中每条边一次且仅一次,并且过每一顶点的回路。
欧拉图—存在欧拉回路的图。欧拉图就是从一顶出发每条边恰通过一次又能回到出发顶点的那种图,即不重复的行遍所有的边再回到出发点。
通路和回路-称vie1e2…envj为一条从 vi到 vj且长度为n的通路,其中长度是指通路中边的条数.称起点和终点相同的通路为一条回路。
简单图-不含平行边和自回路的图。
混合图-既有有向边,也有无向边的图
平凡图-仅有一个结点的图
完全图-有n个结点的且每对结点都有边相连的无向简单图,称为无向完全图;有n个结点的且每对结点之间都有两条方向相反的边相连的有向简单图为有向完全图。
(2)欧拉图的特征:
无向图
a)G有欧拉通路的充分必要条件为:G 连通,G中只有两个奇度顶点(它们分别是欧拉通路的两个端点)。
b)G有欧拉回路(G为欧拉图):G连通,G中均为偶度顶点。
有向图
a)D有欧拉通路:D连通,除两个顶点外,其余顶点的入度均等于出度,这两个特殊的顶点中,一个顶点的入度比出度大1,另一个顶点的入度比出度小1。
b)D有欧拉回路(D为欧拉图):D连通,D中所有顶点的入度等于出度。一个有向图是欧拉图,当且仅当该图所有顶点度数都是0。
代码如下:
#include<iostream>
#include<cstdio>
#include<cstring>
#include<string>
#include<vector>
#include<algorithm>
using namespace std;
int pre[1010];
int unionsearch(int root) //查找根结点
{
int son, tmp;
son = root;
while(root != pre[root])
root = pre[root];
while(son != root) //路径压缩
{
tmp = pre[son];
pre[son] = root;
son = tmp;
}
return root;
}
void join(int root1, int root2) //连通则合并
{
int x, y;
x = unionsearch(root1);
y = unionsearch(root2);
if(x != y)
pre[x] = y;
}
int main()
{
int ncase, v, l, start, end, flag;
scanf("%d", &ncase);
while(ncase--)
{
flag = 1;
memset(pre, 0, sizeof(pre));
vector<int> res[1010];
scanf("%d%d", &v, &l);
for(int i = 1; i <= v; ++i) //初始化
pre[i] = i;
for(int i = 0; i < l; ++i)
{
scanf("%d%d", &start, &end);
res[start].push_back(end);
res[end].push_back(start);
join(unionsearch(start), unionsearch(end));
}
for(int i = 2; i <= v; ++i)
if(unionsearch(1) != unionsearch(i)) //判断是否为连通图
{
flag = 0;
break;
}
if(!flag) //非连通图
printf("No\n");
else //连通图
{
int count = 0;
for(int i = 1; i <= 1000; ++i)
if(res[i].size() & 1) //奇度顶点个数
count++;
if(count == 0 || count == 2) //奇度顶点为0或者2可以一笔画
printf("Yes\n");
else //不可以一笔画
printf("No\n");
}
}
}
int unionsearch(int root) //查找根结点
{
return pre[root] == root ? root : pre[root] = unionsearch(pre[root]);
}
另一种思路:
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
#define MAXN 1010
int pre[MAXN]; //父节点
bool flag[MAXN]; //顶点的奇偶性(很巧妙)
int find(int root) //查找根结点+路径压缩
{
return root == pre[root] ? root : find(pre[root]);
}
void join(int root1, int root2) //合并
{
int x, y;
x = find(root1);
y = find(root2);
if(x != y)
pre[x] = y;
}
int main()
{
int ncase;
int v, l, start, end;
int sum_root, sum_vcount; //根结点的个数,记录奇度顶点个数
scanf("%d", &ncase);
while(ncase--)
{
scanf("%d%d", &v, &l);
for(int i = 1; i <= v; ++i) //初始化
pre[i] = i;
memset(flag, false, sizeof(flag));
sum_root = 0;
sum_vcount = 0;
for(int i = 1; i <= l; ++i)
{
scanf("%d%d", &start, &end);
join(start, end);
flag[start] = !flag[start];//只记录奇偶性,1为奇度,0为偶度
flag[end] = !flag[end];
}
for(int i = 1; i <= v; ++i)
{
if(pre[i] == i) sum_root++; //根结点个数
if(flag[i]) sum_vcount++;//奇度顶点个数
}
if(sum_root > 1)
{
printf("No\n");
continue;
}
else
{
if(sum_vcount == 0 || sum_vcount == 2)
printf("Yes\n");
else
printf("No\n");
}
}
return 0;
}