欧拉路:图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;
}
POJ 2230 Watchcow
题意:
给你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;
}