POJ 1300 判断是否存在欧拉回路(包含定理)

判断存在欧拉回路的定理:

欧拉通路和欧拉回路的判定是很简单的,请看下面的定理及推论。
定理5.1 

无向图G 存在欧拉通路的充要条件是:
G 为连通图,并且G 仅有两个奇度结点(度数为奇数的顶点)或者无奇度结点。
推论5.1:
1) 当G 是仅有两个奇度结点的连通图时,G 的欧拉通路必以此两个结点为端点。
2) 当G 是无奇度结点的连通图时,G 必有欧拉回路。
3) G 为欧拉图(存在欧拉回路)的充分必要条件是G 为无奇度结点的连通图。

定理5.2 

有向图D 存在欧拉通路的充要条件是:
D 为有向图,D 的基图连通,并且所有顶点的出度与入度都相等;或者除两个顶点外,其余
顶点的出度与入度都相等,而这两个顶点中一个顶点的出度与入度之差为1,另一个顶点的出度
与入度之差为-1。
推论5.2:
1) 当D 除出、入度之差为1,-1 的两个顶点之外,其余顶点的出度与入度都相等时,D 的
有向欧拉通路必以出、入度之差为1 的顶点作为始点,以出、入度之差为-1 的顶点作为
终点。
2) 当D 的所有顶点的出、入度都相等时,D 中存在有向欧拉回路。
3) 有向图D 为有向欧拉图的充分必要条件是D 的基图为连通图,并且所有顶点的出、入度
都相等。


一:无向图的判定:

POJ 1300

题目描述:
你是一座大庄园的管家。庄园有很多房间,编号为0、1、2、3,...。你的主人是一个心不在
焉的人,经常沿着走廊随意地把房间的门打开。多年来,你掌握了一个诀窍:沿着一个通道,穿
过这些大房间,并把房门关上。你的问题是能否找到一条路径经过所有开着门的房间,并使得:
1) 通过门后立即把门关上;
2) 关上了的门不再打开;
3) 最后回到你自己的房间(房间0),并且所有的门都已经关闭了。
在本题中,给定房间列表,及连通房间的、开着的门,并给定一个起始房间,判断是否存在
这样的一条路径。不需要输出这样的路径,只需判断是否存在。假定任意两个房间之间都是连通
的(可能需要经过其他房间)。
输入描述:
输入文件包含多个(最多可达100 个)测试数据,每个测试数据之间没有空行隔开。
每个测试数据包括3 部分:
起始行-格式为“START M N”,其中M 为管理员起始所处的房间号,N 为房间的总数(1
≤N≤20);
房间列表-一共N 行,每行列出了一个房间通向其他房间的房间号(只需列出比它的号码大
的房间号,可能有多个,按升序排列),比如房间3 有门通向房间1、5、7,则房间3 的信息行内
容为“5 7”,第一行代表房间0,最后一行代表行间N-1。有可能有些行为空行,当然最后一行肯
定是空行,因为N-1 是最大的房间号;两个房间之间可能有多扇门连通。
终止行-内容为"END"。
输入文件最后一行是"ENDOFINPUT",表示输入结束。
输出描述:
每个测试数据对应一行输出,如果能找到一条路关闭所有的门,并且回到房间0,则输出"YES
X",X 是他关闭的门的总数,否则输出"NO"。


分析:
以房间为顶点、连接房间之间的门为边构造图。根据题目的意思,输入文件中每个测试数据
所构造的图都是连通的。本题实际上是判断一个图中是否存在欧拉回路或欧拉通路,要分两种情
况考虑:
1) 如果所有的房间都有偶数个门(通往其他房间),那么有欧拉回路,可以从0 号房间出发,
回到0 号房间。但是这种情况下,出发的房间必须为0,因为要求回到0 号房间。例如,
第1 个测试数据所对应的图为图5.6(a),图中有浅色阴影的顶点(即顶点0),表示管家
初始时所处的房间;在该测试数据中,管家可以回到0 号房间。
2) 有两个房间的门数为奇数,其余的都是偶数,如果出发的房间和0 号房间的门数都是奇
数,那么也可以从出发的房间到达0 号房间,并且满足题目要求。但是不能从房间0 出
发,必须从另一个门数为奇数的房间出发。例如第2、3 个测试数据就是这种情形,对应
的图为图(b)和图(c),输出的结果分别是"NO"和"YES 7"。
对于庄园的其他情形,都是不能完成题目中所要求的任务的,所以直接输出"NO"。
本题的难点在于输入数据的处理:可以使用sscanf函数处理数据。

#include <iostream>
#include <cstdio>
#include <fstream>
#include <algorithm>
#include <cmath>
#include <deque>
#include <vector>
#include <list>
#include <queue>
#include <string>
#include <cstring>
#include <map>
#define PI acos(-1.0)
#define mem(a,b) memset(a,b,sizeof(a))
#define sca(a) scanf("%d",&a)
#define pri(a) printf("%d\n",a)
#define M 202
#define INF 100000001
using namespace std;
typedef long long ll;
int main()
{
    char a[M];
    int m,n;
    while(cin>>a)
    {
        if(strcmp(a,"ENDOFINPUT")==0) break;
        else cin>>m>>n;
        int door[101]={0},i,j,num=0,odd=0;getchar();
        for(i=0;i<n;i++)
        {
            int k=0;

            gets(a);
            while(sscanf(a+k,"%d",&j)==1) //依次取出整形数据
            {
                num++;
                door[i]++; door[j]++;
                while(a[k]&&a[k]==' ') k++;
                while(a[k]&&a[k]!=' ') k++;
            }
        }
        cin>>a;
        for(i=0;i<n;i++)
        {
            if(door[i]&1) odd++;  //看看有多少个点度数是奇点……
        }
        if(odd==0&&m==0) printf("YES %d\n",num); //如果没有奇点,而起始点是0的可以最后回到0点符合,如果第一个样例
        else if(odd==2&&door[m]&1&&door[0]&1&&m!=0)  //如果有2个奇点,且这两个起始点是0和另一个,并且起始点是另一个才能回到0点,如第三个样例
            printf("YES %d\n",num);
        else printf("NO\n");  //第二个样例
    }
    return 0;
}

二:有向图的判定

POJ 1386

题目描述:
有些秘门带有一个有趣的词迷。考古学家必须解开词迷才能打开门。由于没有其他方法可以
打开门,因此词迷就变得很重要。
每个门上有许多磁盘。每个盘上有一个单词,这些磁盘必须重新排列使得每个单词第一个字
母跟前一个单词最后一个字母相同。例如单词"acm"可以跟在单词"motorola"的后面。你的任务是
编写一个程序,读入一组单词,然后判定是否可以经过重组使得每个单词第一个字母跟前一个单
词最后一个字母相同,这样才能打开门。
输入描述:
输入文件中包含T 个测试数据。输入文件的第一行就是T,接下来是T 个测试数据。每个测
试数据的第一行是一个整数N,表示单词的个数(1≤N≤100000);接下来有N 行,每行是一个
单词;每个单词至少有2 个、至多有1000 个小写字母,即单词中只可能出现字母'a'~'z';在同一
个测试数据中,一个单词可能出现多次。
输出描述:
如果通过重组单词可以达到要求,输出"Ordering is possible.",否则输出"The door cannot be
opened."。


分析:
在本题中,每个单词只有首尾两个字母很关键,并且每个单词可以看成连接首尾两个字母的
一条有向边(由首字母指向尾字母)。这样每个测试数据中的一组单词可以构造成一个图:图中的
顶点为26 个小写字母,每个单词为图中的一条边。
构造好有向图后,题目要判定是否可以经过重组使得每个单词第一个字母跟前一个单词最后
一个字母相同,等效于判断图中是否存在一条路径经过每条边一次且仅一次,这就是有向欧拉通
路。本题的处理方法是:


(1) 读入每个单词时,因为每个单词相当于一条从首字母指向尾字母的边,所以对单词首字母
对应的顶点,出度加1;尾字母对应的顶点,入度加1;
(2) 26 个顶点的入度和出度都统计完毕后,根据各顶点的出度、入度关系来判断是否存在欧
拉通路,但要注意排除每个单词的首尾字母中没有出现过的字母。在下面的代码中,用bused[ ]
数组来表示每个字母是否在单词的首尾中出现。
(3) 判断完以后,还得判断整个有向图的基图(即不考虑边的方向)是否连通,如图5.7(b)
所示,每个顶点的出度、入度都相等,但这个图的基图是不连通的,所以也不存在有向欧拉通路。
判断连通最好的方法是使用并查集:考察图中所有的边(u, v),如果u 和v 不相同,且u 和v 不在
同一个连通分量上,则合并u 和v 各自所在的连通分量;

即利用并查集判断连通不连通!

#include <iostream>
#include <cstdio>
#include <fstream>
#include <algorithm>
#include <cmath>
#include <deque>
#include <vector>
#include <list>
#include <queue>
#include <string>
#include <cstring>
#include <map>
#define PI acos(-1.0)
#define mem(a,b) memset(a,b,sizeof(a))
#define sca(a) scanf("%d",&a)
#define pri(a) printf("%d\n",a)
#define M 100002
#define INF 100000001
using namespace std;
typedef long long ll;
int t,n,od[26],id[26],Exit[26],father[26],rank[26];
char str[1002];
struct edge
{
    int u,v;
}e[M];
int find(int x)
{
    return father[x]==x?x:father[x]=find(father[x]);
}
void uni(int u,int v)
{
    if(rank[u]>rank[v]) father[v]=u;
    else
    {
        if(rank[u]==rank[v]) rank[v]++;
        father[u]=v;
    }
}
int euler()
{
    int u,v,i,j=-1;
    for(i=0;i<26;i++)
    {
        father[i]=i;  //并查集初始化
        rank[i]=0;
    }
    for(i=0;i<n;i++)
    {
        u=find(e[i].u); v=find(e[i].v);
        if(u!=v) uni(u,v);
    }
    for(i=0;i<26;i++)
    {
        if(Exit[i]==0) continue;
        if(j==-1) j=i;  //标记第一个exit[i]不为0的顶点
        else if(find(i)!=find(j)) break; //不连通
    }
    if(i<26) return 0; //不连通
    return 1;  //连通
}
int main()
{
    int u,v,i;
    sca(t);
    while(t--)
    {
        mem(od,0);
        mem(id,0);
        mem(Exit,0);
        sca(n);
        for(i=0;i<n;i++)
        {
            scanf("%s",str);
            u=str[0]-'a';
            v=str[strlen(str)-1]-'a';
            od[u]++; id[v]++;
            Exit[u]=Exit[v]=1;
            e[i].u=u; e[i].v=v;
        }
        int flag=1,od1=0,id1=0;
        for(i=0;i<26;i++)
        {
            if(Exit[i]==0) continue;
            if(od[i]-id[i]>1||id[i]-od[i]>1) {flag=0;break;}  //因为只有一条路径连接所有的单词
            if(od[i]==0&&id[i]==0) {flag=0;break;}
            if(od[i]-id[i]==1)
            {
                od1++;
                if(od1>1) {flag=0;break;}
            }
            if(id[i]-od[i]==1)
            {
                id1++;
                if(id1>1) {flag=0;break;}
            }
        }
        if(od1!=id1) flag=0;
        else if(!euler()) flag=0;
        if(flag) printf("Ordering is possible.\n");
        else printf("The door cannot be opened.\n");
    }
    return 0;
}


  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值