一笔画问题
时间限制:3000 ms | 内存限制:65535 KB
难度:4
描述
zyc从小就比较喜欢玩一些小游戏,其中就包括画一笔画,他想请你帮他写一个程序,判断一个图是否能够用一笔画下来。
规定,所有的边都只能画一次,不能重复画。
输入
第一行只有一个正整数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
此题考察判断图连通+欧拉图性质
判断连通图有三种方法DFS+BFS+并查集
DFS:
时间复杂度:O(n+e),因为在遍历图时,对图中每个顶点至多调用一次dfs,因为一旦某个顶点被标志成已访问,就不再从它出发进行搜索。又采用的是邻接表结构,找邻接结点时间为O(e),所以最终为O(n+e),下面的代码因为没有及时删除已访问的邻接结点,所以实际为O(n+2e)
#include <iostream>
#include <vector>
#include <cstring>
using namespace std;
const int MAXN = 100000;
vector<int> g[MAXN];
bool vis[MAXN];
void dfs(int node){
int i;
vis[node] = true;
for( i = 0 ; i < g[node].size() ; i ++ ){
if(!vis[g[node][i]])
dfs(g[node][i]);
}
}
bool Judge(int node_num){
int i;
for( i = 1 ; i <= node_num ; i ++ )
if(!vis[i])
return false;
return true;
}
int main(){
int node_num,edge_num,node1,node2,i;
// cout << boolalpha << vis[20] << endl; 输出false,否则默认用0代替
while(cin >> node_num >> edge_num){ //接收多组数据
for( i = 1 ; i <= node_num ; i ++ )
g[i].clear(); //删除vector中所有元素
memset(vis,false,sizeof(vis));
for( i = 0 ; i < edge_num ; i ++ ){
scanf("%d%d",&node1,&node2);
g[node1].push_back(node2); //将无向图当作有向图处理,正反两次接入连接表
g[node2].push_back(node1); //添加到末尾
}
dfs(1);
if(Judge(node_num))
cout << "YES" << endl;
else cout << "NO" << endl;
}
return 0;
}
BFS:
每个顶点至多进一次队列,遍历图的过程实际是通过边或弧找邻接结点的过程,所以和DFS相同,不同之处在于顶点访问顺序不同
用下面的bfs代替上面的dfs即为完整的BFS程序
void bfs(int node){
int x,i;
queue<int> q;
q.push(node);
while(!q.empty()){
x = q.front(); //返回队列第一个元素
vis[x] = true;
q.pop() ; //删除队首元素
for( i = 0 ; i < g[x].size() ; i ++ ) {
if(vis[g[x][i]])
g[x].erase(g[x].begin() + i); //删除图中已经遍历的点,提高遍历速度
else q.push(g[x][i]);
}
}
}
测试样例
6 6
6 1
3 4
2 3
1 2
5 6
5 3
----连通
4 3
1 2
1 3
1 4
----连通
用并查集判断图连通时,方法如下:
初始化(均为-1)时将每个结点看作一个集合,
I.则每给出一条边即把两个集合合并(father[i]=j)。最后遍历所有点,
有几个集合(根据最终结点father是否为-1来定)便有几个连通分量,若只有一个集合说明图连通
II.每给出一条边时判定两个结点所在集合的根结点是否相同,在不同时合并,同时用count计数,表示新增一条边,若最终count>=N-1(N为顶点个数) ,则图连通(代码采用的是方法II)
然后结合欧拉图性质(奇度数的顶点个数为0或2方可一笔画)
#include <stdio.h>
#include <string.h>
int Find_Father(int num,int *father){ //找到num所在的连通分量,含路径压缩
return father[num] == -1 ? num : Find_Father(father[num],father);
}
int main(){
int N,P,Q,A,B,count;
int indegree[1001],father[1001];
scanf("%d",&N);
while(N --){
scanf("%d%d",&P,&Q);
memset(indegree,0,sizeof(indegree));
memset(father,-1,sizeof(father));
count = 0;
while(Q --){
scanf("%d%d",&A,&B);
indegree[A] ++;
indegree[B] ++;
A = Find_Father(A,father);
B = Find_Father(B,father);
if( A != B){
father[A] = B; //合并有边相连的不同的连通分量
count ++; //新增边
}
}
if(count < P - 1){
printf("No\n");
continue; //跳过一次while循环
}
count = 0; //当图为连通时,count用于计奇度数顶点个数
while(P){
if(indegree[P] % 2)
count ++;
if(count > 2)
break;
P --;
}
if(count == 0 || count == 2)
printf("Yes\n");
else printf("No\n");
}
return 0;
}
参考资料:
http://blog.csdn.net/fangpinlei/article/details/42127055