歐拉回路:圖G,若存在一條路,經過G中每條邊有且僅有一次,稱這條路為歐拉路,如果存在一條回路經過G每條邊有且僅有一次,
稱這條回路為歐拉回路。具有歐拉回路的圖成為歐拉圖。
判斷歐拉路是否存在的方法
有向圖:圖連通,有一個頂點出度大入度1,有一個頂點入度大出度1,其余都是出度=入度。
無向圖:圖連通,只有兩個頂點是奇數度,其余都是偶數度的。
判斷歐拉回路是否存在的方法
有向圖:圖連通,所有的頂點出度=入度。
無向圖:圖連通,所有頂點都是偶數度。
程序實現一般是如下過程:
1.利用並查集判斷圖是否連通,即判斷p[i] < 0的個數,如果大於1,說明不連通。
2.根據出度入度個數,判斷是否滿足要求。
3.利用dfs輸出路徑。
比較好的題目是poj2337,判斷單詞是否連成一排的。
hdu3018
給出N個節點,M個邊,問要遍歷一遍所有的邊,需要的最小group數目。
求一個圖中最少幾筆畫,利用歐拉回路性質,首先得到圖的每個強連通分支,然后計算每個強連通分支每個是否都是偶數度是的話,一筆解決,否的話,需要奇數度節點個數的1/2筆解決。(當一個節點度數為奇數時,我們只需要令它的度數為1,因為偶數的話直接抵消了,最后判斷一個scc中未1的節點個數,除以2就得到該scc的最小筆畫了)
代碼:

#include <iostream> #include <stdio.h> using namespace std; const int maxv = 100000 + 2; int odds[maxv]; /* 頂點 */ int du[maxv]; /* 每個頂點的度數 */ int p[maxv]; /* 並查集數組 */ bool used[maxv]; int scc[maxv]; /* scc個數 */ void init(int n) { for(int i = 0; i <= n; ++i) { odds[i] = 0; p[i] = -1; du[i] = 0; used[i] = 0; } } int utf_find(int x) { if(0 <= p[x]) { p[x] = utf_find(p[x]); return p[x]; } return x; } void utf_union(int a, int b) { int r1 = utf_find(a); int r2 = utf_find(b); if(r1 == r2) return; int n1 = p[r1]; int n2 = p[r2]; if(n1 < n2) { p[r2] = r1; p[r1] += n2; } else { p[r1] = r2; p[r2] += n1; } } int main() { int n = 0; int m = 0; int a = 0; int b = 0; int i = 0; int cnt = 0; while(scanf("%d%d", &n, &m) != EOF) { init(n); cnt = 0; for(i = 1; i <= m; ++i) { scanf("%d%d", &a, &b); du[a]++; du[b]++; utf_union(a, b); } for(i = 1; i <= n; ++i) { int f = utf_find(i); if(!used[f]) { used[f] = 1; scc[cnt++] = f; } if(1 == du[i]%2) odds[f]++; } int ret = 0; for(i = 0; i < cnt; ++i) { if(0 == du[scc[i]]) continue; if(0 == odds[scc[i]]) ++ret; else ret += odds[scc[i]]/2; } printf("%d\n", ret); } return 0; }
poj1386
給出n個單詞,如果一個單詞的尾和另一個單詞的頭字符相等,那么可以相連,問這n個單詞是否可以排成一列。歐拉路應用,構圖:一個單詞的頭尾字母分別作為頂點,每輸入一個word,該word的頭指向word的尾畫一個有向邊,並且記錄每個頂點的出入度。利用並查集先判斷是否為scc,如果是的話則判斷是否奇數度節點為0或者只有2個。
代碼:

#include <iostream> #include <stdio.h> using namespace std; const int max_len = 1000 + 10; const int maxv = 27; int in[maxv]; /* 入度 */ int out[maxv]; /* 出度 */ int p[maxv];/* 並查集數組 */ bool used[maxv];/* 標識字符是否出現在圖中 */ void init() { for(int i = 0; i < maxv; ++i) { in[i] = 0; out[i] = 0; p[i] = -1; used[i] = 0; } } int find_set(int x) { if(0 <= p[x]) { p[x] = find_set(p[x]); return p[x]; } return x; } void union_set(int a, int b) { int r1 = find_set(a); int r2 = find_set(b); if(r1 == r2) return; int n1 = p[r1]; int n2 = p[r2]; if(n1 < n2) { p[r2] = r1; p[r1] += n2; } else { p[r1] = r2; p[r2] += n1; } } int main() { int t = 0; int n = 0; int len = 0; int s = 0; int e = 0; int i = 0; char word[max_len]; scanf("%d", &t); while(t--) { init(); scanf("%d", &n); for(i = 0; i < n; ++i) { scanf("%s", word); len = strlen(word); s = word[0] - 'a'; e = word[len - 1] - 'a'; used[s] = 1; used[e] = 1; out[s]++; in[e]++; union_set(s, e); } //根據並查集判斷圖是否連通 int scc = 0; for(i = 0; i < maxv; ++i) { if(used[i] && 0 > p[i]) ++scc; } if(1 < scc) { printf("The door cannot be opened.\n"); continue; } //入度是否等於出度 int a = 0; int b = 0; for(i = 0; i < maxv; ++i) { if(used[i] && in[i] != out[i]) { if(1 == (in[i] - out[i])) ++a; else if(1 == (out[i] - in[i])) ++b; else break; } } if(i < maxv) printf("The door cannot be opened.\n"); else if(0 == (a + b) || (1 == a && 1 == b)) printf("Ordering is possible.\n"); else printf("The door cannot be opened.\n"); } return 0; }
poj2230
題目大意:給出n個field及m個連接field的邊,然后要求遍歷每條邊僅且2次,求出一條路徑來。
這個題目典型歐拉回路,由於題目保證了肯定存在,所以我們直接dfs就行,首先有個小技巧是如何判斷該條路是否走過了,也就是我們得對有向邊進行標記。利用之前的結構
struct edge{
int next;
int to;
};
edge node[maxm];
int adj[maxv] = {-1}
每一條邊對應了一個next,我們只需要對next標記就可以了。

#include <iostream> #include <stdio.h> using namespace std; const int maxm = 2*50000 + 1; const int maxv = 10000 + 5; struct edge{ int to; int next; }; edge node[maxm]; /*鄰接表*/ int adj[maxv]; bool used[maxm];/* 標記邊是否訪問過*/ void Euler(int vertix) { for(int i = adj[vertix]; i != -1; i = node[i].next) { if(!used[i]) { used[i] = 1; Euler(node[i].to); } } printf("%d\n", vertix); } int main() { int n = 0; int m = 0; int i = 0; int u = 0; int v = 0; int cnt = 0; scanf("%d%d", &n, &m); for(i = 0; i <= n; ++i) adj[i] = -1; for(i = 0; i <= m*2; ++i) used[i] = 0; for(i = 0; i < m; ++i) { scanf("%d%d", &u, &v); //u->v node[cnt].to = v; node[cnt].next = adj[u]; adj[u] = cnt++; //v->u node[cnt].to = u; node[cnt].next = adj[v]; adj[v] = cnt++; } Euler(1); return 0; }
poj2337
求歐拉路徑和poj1386同一個題,只不過這個題目需要輸出歐拉路徑。
題目大意:給出一組單詞,如果兩個單詞,一個單詞的頭和另一個單詞的尾相同,則可以相連,例如abce, efdg,可以相連,問這組單詞能否排成一排,如果可以求出字典序自小的那個。
構圖:單詞作為邊,單詞的頭字母和尾字母分別作為頂點,讀入一個單詞,添加一條邊,並且用鄰接表來存邊,首先利用並查集判斷圖是否連通,然后再判斷是否可以構成歐拉通路或者回路,如果是回路,則從a開始找,找到第一個存在的字母,從這個字母遍歷就行,如果是通路,則必須從出度大於入度1的那個頂點開始遍歷。由於這個題目要求最小字典順序,然后考慮我們建立鄰接表的時候是采用頭插法,那么我們如果將單詞從小到大排序,由於我們遍歷頂點肯定是從小的頂點開始的,這樣遍歷一個頂點的鄰接邊的時候就會從大到小訪問這個頂點的邊,無法滿足字典最小,所以從大到小排序,遍歷依node數組為准,每次遍歷一條邊設置該下標已訪問過。
代碼:

#include <iostream> #include <algorithm> using namespace std; const int maxn = 1000 + 10; const int max_l = 28; //char word[maxn][max_l]; char ret[maxn][max_l]; struct edge{ int to; int next; char str[max_l]; }; edge node[maxn]; int adj[max_l]; int in[max_l]; int out[max_l]; bool used[maxn]; bool exist[max_l]; int p[max_l]; /* 並查集 */ int num_e = 0; int ret_e = 0; //從大到小排序 bool cmp(edge p1, edge p2) { return strcmp(p1.str, p2.str) > 0; } void init() { for(int i = 0; i < max_l; ++i) { in[i] = 0; out[i] = 0; p[i] = -1; exist[i] = 0; adj[i] = -1; } for(int j = 0; j < maxn; ++j) used[j] = 0; num_e = 0; ret_e = 0; } int find_set(int u) { if(0 <= p[u]) { p[u] = find_set(p[u]); return p[u]; } return u; } void union_set(int u, int v) { int r1 = find_set(u); int r2 = find_set(v); if(r1 == r2) return; int n1 = p[r1]; int n2 = p[r2]; if(n1 < n2) { p[r2] = r1; p[r1] += n2; } else { p[r1] = r2; p[r2] += n1; } } void Eular(int vertix, int idx) { for(int i = adj[vertix]; i != -1; i = node[i].next) { if(!used[i]) { // strcpy(ret[ret_e++], node[i].str); used[i] = 1; Eular(node[i].to, i); } } /*idx就是node數組的下標,標識一條邊*/ if(0 <= idx) strcpy(ret[ret_e++], node[idx].str); } int main() { int t = 0; int n = 0; int i = 0; int u = 0; int v = 0; int start = 0; //從哪個頂點開始遍歷 scanf("%d", &t); while(t--) { scanf("%d", &n); if(!n) continue; for(i = 0; i < n; ++i) scanf("%s", node[i].str); init(); start = max_l; sort(node, node + n, cmp); //建圖 for(i = 0; i < n; ++i) { u = node[i].str[0] - 'a'; v = node[i].str[strlen(node[i].str) - 1] - 'a'; in[v]++; out[u]++; exist[u] = 1; exist[v] = 1; union_set(u, v); node[num_e].to = v; node[num_e].next = adj[u]; adj[u] = num_e++; } //判斷是否連通 int scc = 0; for(i = 0; i < max_l; ++i) { if(exist[i] && 0 > p[i]) ++scc; if(1 < scc) break; } if(1 < scc) //不連通 { printf("***\n"); continue; } //是通路or回路 int a = 0; int b = 0; start = -1; for(i = 0; i < max_l; ++i) { if(exist[i] && in[i] != out[i]) { if(1 == in[i] - out[i]) ++a; else if(1 == out[i] - in[i]) { ++b; start = i; } else break; } } if(i < max_l) { printf("***\n"); continue; } else { if(!((0 == a + b) || (1 == a && 1 == b))) { printf("***\n"); continue; } if(-1 == start) {//回路 找到第一個存在的字母 int k = 0; for(k = 0; k < max_l; ++k) { if(out[k]) break; } start = k; } //從頂點start開始dfs Eular(start, -1); printf("%s", ret[ret_e - 1]); for(i = ret_e - 2; i >= 0; --i) printf(".%s", ret[i]); printf("\n"); } } return 0; }