欧拉回路

欧拉路:图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;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值