zoukankan      html  css  js  c++  java
  • 欧拉回路

    欧拉回路:图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的最小笔画了)

    代码:

    View Code
    #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个。

    代码:

    View Code
    #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标记就可以了。

    View Code
    #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数组为准,每次遍历一条边设置该下标已访问过。

    代码:

    View Code
    #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;
    }
  • 相关阅读:
    A % B Problem
    封锁阳光大学
    数楼梯
    海滩防御
    修复公路
    四子连棋
    口袋的天空
    兔子数
    逆序对&求逆序对
    【模板】单源最短路径*
  • 原文地址:https://www.cnblogs.com/buptLizer/p/2450297.html
Copyright © 2011-2022 走看看