欧拉路:图G,若存在一条路,经过G中每条边有且仅有一次,称这路为欧拉路。
欧拉回路:图G,若存在一条回路,经过G中每条边有且仅有一次,称这条路为欧拉回路。
判断欧拉路是否存在的方法
有向图:图连通,有一个顶点出度大入度1,有一个顶点入度大出度1,其余都是出度=入度。
无向图:图连通,只有两个顶点是奇数度,其余都是偶数度的。
判断欧拉回路是否存在的方法
有向图:图连通,所有的顶点出度=入度。
无向图:图连通,所有顶点都是偶数度。
HDU 3018 Ant Trip
题意:
给你n个点和m条边,问需要多少一笔画遍历所有的边。
解题思路:
经典的一笔画问题,对于每一个连通分量,如果每个节点度数为偶数,直接欧拉回路一笔画完成。如果存在节点为奇数的,对于每个奇数节点必然是起始节点或者终止节点,所以笔画数为奇数个数cnt/2.利用并查集来判断联通块。
/* ********************************************** Author : JayYe Created Time: 2013/10/1 11:32:28 File Name : JayYe.cpp *********************************************** */ #include <stdio.h> #include <string.h> #include <algorithm> using namespace std; const int maxn = 100000 + 5; int fa[maxn], du[maxn], ji[maxn], cnt[maxn]; void init(int n) { for(int i = 1;i <= n; i++) { fa[i] = i; du[i] = ji[i] = cnt[i] = 0; } } // 并查集 int find_fa(int x) { return fa[x] = fa[x] == x ? x : find_fa(fa[x]); } void Union(int x, int y) { x = find_fa(x); y = find_fa(y); if(x != y) fa[x] = y; } int main() { int n, m, u, to; while(scanf("%d%d", &n, &m) != -1) { init(n); for(int i = 0;i < m; i++) { scanf("%d%d", &u, &to); du[u]++; du[to]++; Union(u, to); } for(int i = 1;i <= n; i++) fa[i] = find_fa(i), cnt[fa[i]]++; int ans = 0; // 统计每个联通块的奇数个数 for(int i = 1;i <= n; i++) if(du[i] & 1) ji[fa[i]]++; for(int i = 1;i <= n; i++) if(fa[i] == i && cnt[i] > 1) { if(!ji[i]) ans++; else ans += ji[i]/2; } printf("%d\n", ans); } return 0; }
POJ 1386 Play on Words
题意:
给你n个单词,一个单词尾字母和另一个单词的头字母相同的话,则可以相连。问n个单词能否连成一排。
解题思路:
欧拉路的裸题应用。并查集判断是否联通,然后判断入度出度是否满足条件即可。
/* ********************************************** Author : JayYe Created Time: 2013/10/1 13:17:20 File Name : JayYe.cpp *********************************************** */ #include <stdio.h> #include <string.h> #include <algorithm> using namespace std; const int maxn = 30; int fa[maxn], vis[maxn], ru[maxn], chu[maxn]; int find_fa(int x) { return fa[x] = fa[x]==x ? x : find_fa(fa[x]); } void Union(int x, int y) { x = find_fa(x); y = find_fa(y); if(x != y) fa[x] = y; } void init() { for(int i = 0;i < 26; i++) fa[i] = i; memset(vis, 0, sizeof(vis)); memset(ru, 0, sizeof(ru)); memset(chu, 0, sizeof(chu)); } char s[1111]; int main() { int t, n; scanf("%d", &t); while(t--) { init(); scanf("%d", &n); int pre = -1; for(int i = 0;i < n; i++) { scanf("%s", s); int len = strlen(s); chu[s[0]-'a']++; ru[s[len-1]-'a']++; Union(s[0]-'a', s[len-1]-'a'); vis[s[0]-'a'] = vis[s[len-1]-'a'] = 1; if(pre == -1) pre = s[0]-'a'; } for(int i = 0;i < 26; i++) fa[i] = find_fa(i); bool flag = 1; for(int i = 0;i < 26; i++) if(vis[i] && fa[pre] != fa[i]) { flag = 0; break; } if(!flag) puts("The door cannot be opened."); else { int tmp1 = 0,tmp2 = 0; for(int i = 0;i < 26; i++) if(vis[i]) { if(ru[i] == chu[i] + 1) tmp1++; else if(ru[i] + 1 == chu[i]) tmp2++; else if(ru[i] != chu[i]) flag = 0; } if(tmp1 > 1 || tmp2 > 1) flag = 0; if(flag) puts("Ordering is possible."); else puts("The door cannot be opened."); } } return 0; }
题意:
给你n个点和m条无向边,问是否存在一条路径遍历每条边有且仅有两次,并且都是从1节点开始,1节点结束。
解题思路:
欧拉回路输出路径。要遍历每条边两次并且回到原点,很容易想到把无向边变成有向边,问题就成了给你一个有向图,问是否存在欧拉回路。输出路径用的是白书上的dfs方法。
/* ********************************************** Author : JayYe Created Time: 2013/10/1 13:42:45 File Name : JayYe.cpp *********************************************** */ #include <stdio.h> #include <string.h> #include <algorithm> using namespace std; const int maxn = 10000 + 5; struct Edge { int to, next, vis; }edge[maxn*11]; int head[maxn], E; void newedge(int u, int to) { edge[E].to = to; edge[E].vis = 0; edge[E].next = head[u]; head[u] = E++; } void init() { memset(head, -1, sizeof(head)); E = 0; } void dfs(int u) { for(int i = head[u];i != -1;i = edge[i].next) { if(edge[i].vis) continue; edge[i].vis = 1; int to = edge[i].to; dfs(to); // 输出写在dfs(to)上面就错了 printf("%d\n", to); } } int main() { init(); int n, m, u, to; scanf("%d%d", &n, &m); for(int i = 0;i < m; i++) { scanf("%d%d", &u, &to); newedge(u, to); newedge(to, u); } dfs(1); puts("1"); // 回到原点 return 0; }
POJ 2337 Catenyms
题意:
给你n个单词,一个单词尾字母和另一个单词的头字母相同的话,则可以相连。问n个单词能否连成一排。如果能,要求输出字典序最小的一排。
解题思路:
由于字典序要最小,所以不能对于头字母尾字母进行存边了。所以就只对头字母进行存边,如果可以构成回路,就从头字母最小的单词开始输出,如果是欧拉路,那么只能从出度大入度1的头字母开始输出。
/* ********************************************** Author : JayYe Created Time: 2013-10-3 9:17:14 File Name : JayYe.cpp *********************************************** */ #include <stdio.h> #include <string.h> #include <vector> #include <string> #include <iostream> #include <algorithm> using namespace std; #define pb push_back const int maxn = 1000 + 5; struct PP { char s[22]; // 排序从大到小,因为邻接表存的方式是头插法 bool operator < (const PP& a) const { return strcmp(s, a.s) > 0; } }a[maxn]; struct Edge { int to, next, vis; char s[22]; }edge[maxn<<2]; int fa[33], vis[33], ru[33], chu[33], head[33], E; // 并查集 int find_fa(int x) { return fa[x] = (fa[x] == x ? x : find_fa(fa[x])); } void Union(int x, int y) { x = find_fa(x); y = find_fa(y); if(x != y) fa[x] = y; } void init() { for(int i = 0;i < 26; i++) { fa[i] = i; vis[i] = ru[i] = chu[i] = 0; } memset(head, -1, sizeof(head)); E = 0; } // 存边 void newedge(int u, int to, char s[]) { edge[E].to = to; edge[E].vis = 0; strcpy(edge[E].s, s); edge[E].next = head[u]; head[u] = E++; } int tot; char path[maxn][22]; void dfs(int u) { for(int i = head[u];i != -1;i = edge[i].next) { int to = edge[i].to; if(edge[i].vis) continue; edge[i].vis = 1; dfs(to); strcpy(path[tot++], edge[i].s); } } int main() { int t, n; scanf("%d", &t); while(t--) { init(); scanf("%d", &n); for(int i = 0;i < n; i++) scanf("%s", a[i].s); sort(a, a + n); int pre = -1; for(int i = 0;i < n; i++) { int len = strlen(a[i].s); int u = a[i].s[0] - 'a', to = a[i].s[len-1] - 'a'; newedge(u, to, a[i].s); chu[u]++; ru[to]++; vis[u] = vis[to] = 1; if(pre == -1) pre = u; Union(u, to); } for(int i = 0;i < 26; i++) if(vis[i]) fa[i] = find_fa(i); bool flag = 1; // 并查集判联通 for(int i = 0;i < 26; i++) if(vis[i] && fa[i] != fa[pre]) { flag = 0; break; } if(!flag) puts("***"); else { int tmp1 = 0, tmp2 = 0; for(int i = 0;i < 26; i++) if(vis[i]) { if(ru[i] + 1 == chu[i]) tmp1 ++; else if(chu[i] + 1 == ru[i]) tmp2++; else if(chu[i] != ru[i]) flag = 0; } if(tmp1 > 1 || tmp2 > 1) flag = 0; if(!flag) puts("***"); else { int st = -1; // 如果是欧拉通路 if(tmp1) { for(int i = 0;i < 26; i++) if(ru[i] + 1 == chu[i]) { st = i; break; } } // 如果是欧拉回路 else { for(int i = 0;i < 26; i++) if(vis[i] && chu[i]) { st = i; break; } } tot = 0; dfs(st); // 逆序输出路径 for(int i = tot-1;i >= 0; i--) { printf("%s", path[i]); if(i > 0) printf("."); else puts(""); } } } } return 0; }