欧拉路径问题 (附POJ 1041 POJ 2337)

来源:互联网 发布:房地产网络销售传播图 编辑:程序博客网 时间:2024/06/15 00:30

前几天的一次模拟赛中出了一道欧拉路径求方案的题,输出需要字典序,很自然地挂掉了,然后就好好地又学习了一下欧拉路径的相关问题。

欧拉路径就是由著名的七桥问题引出的。欧拉路径是指不重复地经过一个连通图的每一条边从起点到达终点的路径。欧拉回路是最终回到了起点,这样的一条回路。(注意:欧拉路径和欧拉回路是不一样的,名称可能不一样的人叫法不同,但它们是有性质上的不同的,而且不会同时存在于同一个图中。)

先来说说欧拉路的判断:
无向图:
首先保证在一个连通图(并查集或BFS来判断)内,记录每个点的度数。如果所有点的度数都为偶数,那么这个无向图存在欧拉回路;如果有两个点的度数为奇数,那么存在欧拉路径,以两个度数为奇数的点为起点和终点。注意:因为每条边会给两个点提供度数,所以整个图的度数和为偶数,如果有奇数度数的点,也是有偶数个这样的点。
有向图:
同样是连通图内,记录每个点的出度和入度。如果所有点的出度都等于入度,那么存在欧拉回路;存在欧拉路径的条件是,有一个点s出度比入度大一,那么必然存在另一个点t入度比出度大一(上面提到的度数和为偶数),其他所有点入度等于出度,这条欧拉路径起点s终点t。
总结来说,判断一个图是否存在欧拉路径(回路),要先确定是连通图,之后遍历所有的边,计算出每个点的度数(入度出度)即可。

这里就不详细证明了,我也只是略微理解一点,并不知道详细的证明:因为每个点度数为偶数(或者入度等于出度),所以从一条边进入这个点一次必然会对应一次从这个点走出去一次。

下面说说欧拉路输出解:
大部分存在欧拉路的图中,欧拉路都不是唯一的,所以一般输出方案都是要求字典序。欧拉路输出方案只需要DFS,但是不能直接在DFS中输出解,因为会有这种情况发生:

在这个图中,3、5两个点度数为奇数,因为要按照字典序输出,所以3为起点5为终点,如果我们仅仅在DFS中输出访问的点的话,因为字典序,输出的顺序是3、1、2、4、5……但显然答案为3、1、2、4、6、7、5。
于是我们可以想到回溯,这个是可行的,多开一个数组记录,当我们访问到终点或者不能继续走时,判断是否已遍历所有的边即可,下面是一道例题,题解便是用的回溯的方式写的。(不要妄图在DFS中输出答案了,加再多特判也是不可以的,血淋林的教训)

无序字母对
【问题描述】
给定n个各不相同的无序字母对(区分大小写,无序即字母对中的两个字母可以位置颠倒)。请构造一个有n+1个字母的字符串使得每个字母对都在这个字符串中出现。
【输入数据】
第一行输入一个正整数n。
以下n行每行两个字母,表示这两个字母需要相邻。
【输出数据】
输出满足要求的字符串。
如果没有满足要求的字符串,请输出“No Solution”。
如果有多种方案,请输出前面的字母的ASCII编码尽可能小的(字典序最小)的方案
【样例输入】
4
aZ
tZ
Xt
aX
【样例输出】
XaZtX

#include <cstdio> #include <algorithm>using namespace std;int n, s, t, num, hash[200], deg[55], G[55][55], q[100005];bool go[55][55];bool dfs(int i, int num){    q[num] = i;    if(num == n+1) return 1;    for(int j = 1; j <= 52; j++){        if(G[i][j] && !go[i][j]){            go[i][j] = go[j][i] = 1;            if(dfs(j, num+1)) return 1;            go[i][j] = go[j][i] = 0;        }    }    return 0;}int main(){    for(int i = 'A'; i <= 'Z'; i++)        hash[i] = hash[i-1] + 1;    hash['a'-1] = 26;       for(int i = 'a'; i <= 'z'; i++)        hash[i] = hash[i-1] + 1;    scanf("%d", &n);    for(int i = 1; i <= n; i++){        char c[3];         scanf("%s", c);        int u = hash[c[0]], v = hash[c[1]];        G[u][v] = G[v][u] = 1;        deg[u]++;        deg[v]++;    }       for(int i = 1; i <= 52; i++)        if(deg[i]%2){            if(t){                printf("No Solution");                return 0;               }            if(s) t = i; else s = i;        }    if(!s) while(!deg[s]) s++;    dfs(s, 1);    for(int i = 1; i <= n+1; i++)        printf("%c", q[i] < 27 ? 'A'+q[i]-1 : 'a'+q[i]-27);    return 0;}

题目数据:http://pan.baidu.com/s/1sjLwOf7 提取码:9y6w

但是,搜索并不是一个很好的解法,因为它太慢,时间上很多题目是不允许的,所以需要用一种更快的方法。这种方法我没有很好的理解,但是基于它很好写,就没有过多追究理解了。
仔细想一下可以得出欧拉路是由一些环组成的,那么输出欧拉路的过程可以看作是一个个消去这些环的过程,我们用到了栈,从一个点,先访问完它所有的子节点,之后把它入栈(可能是边,也可能是点,依题目要求),DFS结束后按出栈顺序输出路径(边或点)即可。
我理解的就到这里了,只是会写,非常感谢路过的神犇给蒟蒻解释一下。
基本框架就是这个样子的:

void dfs(int u){    for(int i = 0; i < G[u].size(); i++)        if(!vis[G[u][i]){            vis[G[u][i]] = 1;            dfs(G[u][i]);            s.push(G[u][i]);        }}void write(){    while(s.size()){        printf("%d ", s.top());        s.pop();    }}

很好写,却不是那么容易理解。至少比能理解却写不出来的一些数据结构好。(蒟蒻的自述)

下面来看一看具体的题目:

POJ 1041
题意:
 给定一个无向图,让你求一条欧拉路径,起点是第一组所给的边中两个点的较小者,终点是所有点中的最大者。多组答案则按字典序输出。

注意:此题输出的是边,所以入栈的就是边的编号喽。而且需要先判断是否连通图。

#include <cstdio>#include <cstring>#include <algorithm>using namespace std;int n, s, a, b, e;int G[50][2000], l[2000], r[2000], deg[50], sta[2000];bool flag, go[2000]; void bs(int now){    for(int i = 1; i <= G[now][0]; i++){        int j = G[now][i];        if(go[j]) continue;        go[j] = 1;        int nex = l[j] == now ? r[j] : l[j];        bs(nex);        sta[++sta[0]] = j;    }}int main(){    while(scanf("%d %d", &a, &b) && a){        flag = n = 0;        s = min(a, b);        memset(G, 0, sizeof G);         memset(go, 0, sizeof go);        memset(sta, 0, sizeof sta);        memset(deg, 0, sizeof deg);        scanf("%d", &e);        G[a][++G[a][0]] = e;        G[b][++G[b][0]] = e;        l[e] = a, r[e] = b;        deg[a]++, deg[b]++;        n = max(n, max(a, b));        while(scanf("%d %d", &a, &b) && a){            scanf("%d", &e);            G[a][++G[a][0]] = e;            G[b][++G[b][0]] = e;            l[e] = a, r[e] = b;            deg[a]++, deg[b]++;            n = max(n, max(a, b));        }        for(int i = 1; i <= n; i++)            if(deg[i]&1 && i != s){                printf("Round trip does not exist.\n");                flag = 1;  break;            }        if(flag) continue;        for(int i = 1; i <= n; i++)            sort(G[i]+1, G[i]+G[i][0]+1);        bs(s);        for(int i = sta[0]; i; i--)            printf("%d ", sta[i]);        putchar('\n');    }}

POJ 2337
题意:
给定许多单词,让你首尾相接输出一整串,每个单词间用‘.’隔开。做不到则输出无解。

这种首尾相接输出单词的大都是欧拉路问题,而且是有向图的欧拉路,每个单词代表的是一条有向边,由第一个字母指向最后一个字母的有向边。它与无序字母对那道题不同,因为一个单词是不能反转的,所以是有向边。
然而这道题也要求字典序输出(因为是输出单词,所以也是输出边的),这就比较恶心了,因为这个边是单词,字典序不是简简单单的边的编号大小。这真的是极致考验基础语法。

#include <cstdio>#include <cstring>#include <stack>#include <algorithm>using namespace std;int N, n, e, c, beg, end;int in[27], out[27], f[27], G[27][1000];bool vis[1005], app[27];stack <int> sta;int find(int x) {return f[x] = f[x] == x ? x : find(f[x]);}struct edge{    int u, v;    char c[25];    bool operator < (edge rhs) const{        return strcmp(rhs.c, c) < 0;    }}E[1005];void bs(int now){    for(int i = G[now][0]; i; i--)   // 原本是正序的,不过边按从小到大排(上面的重载运算符是>=)         if(!vis[G[now][i]]){         // 怎么也输出不了正确答案,于是急中生智改成了逆序。             vis[G[now][i]] = 1;            bs(E[G[now][i]].v);            sta.push(G[now][i]);        }}int main(){    scanf("%d", &N);    while(N--){        scanf("%d", &n);        memset(app, 0, sizeof app);        memset(vis, 0, sizeof vis);        memset(out, 0, sizeof out);        memset(in, 0, sizeof in);        memset(E, 0, sizeof E);        memset(G, 0, sizeof G);        vis[0] = e = 1, c = -1, beg = end = 0;        for(int i = 0; i < 27; i++) f[i] = i;        for(int i = 1; i <= n; i++, e++){            scanf("%s", E[i].c);            E[e].u = E[i].c[0]-'a'+1;            E[e].v = E[i].c[strlen(E[i].c)-1]-'a'+1;        }        sort(E+1, E+e+1);        for(int i = 1; i <= n; i++){            out[E[i].u]++;            in[E[i].v]++;               G[E[i].u][++G[E[i].u][0]] = i;            c += !app[E[i].u], app[E[i].u] = 1;            c += !app[E[i].v], app[E[i].v] = 1;            int fu = find(E[i].u), fv = find(E[i].v);            if(fu != fv) f[fu] = fv, c--;        }           for(int i = 1; i < 27; i++){            if(in[i] == out[i]) continue;            if(in[i]+1 == out[i]){                if(beg) {c = 1; break;}                else beg = i;            }            else if(in[i] == out[i]+1){                if(end) {c = 1; break;}                else end = i;            }            else {c = 1; break;}        }        if(c){puts("***"); continue;}        if(!beg) while(!app[beg]) beg++;        bs(beg);        while(sta.size()){            int i = sta.top(); sta.pop();            printf("%s", E[i].c);            if(sta.size()) putchar('.');        }        if(N) putchar('\n');    }}
0 0
原创粉丝点击