zoukankan      html  css  js  c++  java
  • Catenyms (POJ2337) 字典序最小欧拉路

     // 很久不写图论,连模板也不熟悉了0.0

     // 这题是一个技巧性比较高的暴力DFS

    Catenyms

    题目大意

        定义catenym为首尾字母相同的单词组成的单词对, 例如:

    dog.gopher
    gopher.rat
    rat.tiger
    aloha.aloha
    arachnid.dog

        而catenym系统则是多个利用该规则组成的单词串,例如:

    aloha.aloha.arachnid.dog.gopher.rat.tiger 

        给定一组单词,让你判断是否能组成catenym系统,且每个单词只能使用一次。

        无解时输出“***”。

     

    数据范围

        单词长度 1~20,单词个数 3 <= n <= 1000

     

    题目解答

        图论专场的练习题,一看要输出所有单词的序列,感觉就是拓扑排序。

        于是想当然地把每个单词作为顶点,能首尾相连的关系作为边。如果能得到这个图的拓扑排序(每个单词都刚好用上了),并且保持字典序,那么刚好就是本问题的解。

        问题在于,拓扑排序必须是有向无环图(DAG),而单词之间的边关系很容易就成环了,这个方法就行不通了O.O

        

        经过队友指点,发现可以把26个字母当作顶点,单词作为边,题意也就是要找到一条通过所有边的路径,即求解字典序最小的欧拉路!!!

        来复习一下离散数学的知识:

    1.无向连通图 G 是欧拉图,当且仅当 G 不含奇数度结点( G 的所有结点度数为偶数);
    2.无向连通图 G 含有欧拉通路,当且仅当 G 有零个或两个奇数度的结点;
    3.有向连通图 D 是欧拉图,当且仅当该图为连通图且 D 中每个结点的入度=出度;
    4.有向连通图 D 含有欧拉通路,当且仅当该图为连通图且 D 中除两个结点外,其余每个结点的入度=出度 。

        所以怎么获得欧拉路呢?

    • 需要首先判断是否是欧拉图(具有欧拉回路)或半欧拉图(具有欧拉通路):只需要记录每个点的出度和入度,根据(半)欧拉图相关定理判定即可。
    • dfs判断连通性
    • 逆序输出dfs序列 (建图时按字典序排序后加入边)

        一开始直接看了题解也是一头雾水,最终形成了自己理解的算法。采用邻接表的写法:

    #include<cstdio>
    #include<iostream>
    #include<cstdlib>
    #include<string>
    #include<cstring>
    #include<vector>
    #include<algorithm>
    
    using namespace std;
    
    struct Edge {
        int to;
        int id;              // 边的编号   letters[id] = letter
        bool vis;            // dfs是否访问过
    //    string letter;
        Edge(int v, int i): to(v), id(i) {
            vis = false;
        }
    };
    vector<Edge> edge[30];
    string letters[1010];
    int in[30], out[30];     // 顶点的入度与出度
    int ans[1010], cnt;
    
    void init() {
        cnt = 0;
        memset(in, 0, sizeof(in));
        memset(out, 0, sizeof(out));
        for(int i=0;i<26;i++) {
            edge[i].clear();
        }
    
    }
    
    void dfs(char u) {
        for(int i=0;i<edge[u].size();i++) {
            if(!edge[u][i].vis) {
                edge[u][i].vis = true;
    //            cout<<edge[u][i].letter<<'-';        
                
                dfs(edge[u][i].to);
                ans[cnt++] = edge[u][i].id;  // 这行是得到答案的关键,逆序存储字典序最小的路径
            }
        }
    }
    
    int main()
    {
        int T, n;
        scanf("%d", &T);
        while(T--) {
            scanf("%d", &n);
    
            init();
            int st = 26;
            for(int i=1;i<=n;i++) {
                char s[25];
                scanf("%s", s);
                letters[i] = s;
            }
            
            sort(letters+1, letters+1+n);
    
            for(int i=1;i<=n;i++) {
                int u = letters[i][0] - 'a';
                int v = *(letters[i].end()-1) - 'a';
                edge[u].push_back(Edge(v, i));
                out[u]++;
                in[v]++;
                st = min(st, u);
            }
    
            int num = 0;
            bool can = true;
            for(int i=0;i<26;i++) {
                if(out[i]-in[i]==1) {
                    ++num;
                    if(num==1) {
                        st = i;
                    } else {
                        can = false;
                        break;
                    }
                } else if(out[i]-in[i]>1 || in[i]-out[i]>1) {
                    can = false;
                    break;
                }
            }
    
            if(!can) {
                printf("***
    ");
                continue;
            }
    //        cout<<st<<endl;
            dfs(st);
            if(cnt!=n) {
                printf("***
    ");
                continue;
            }
            
            for(int i=cnt-1;i>=0;i--) {
                printf("%s%c", letters[ans[i]].c_str(), i!=0?'.':'
    ');
            }
    
        }
        return 0;
    }

        参考别人没有建图的代码0ms,采用并查集缩点,判断是否联通:

    #include<iostream>
    #include<cstdio>
    #include<string.h>
    #include<cstring>
    #include<algorithm>
    using namespace std;
    // 来源:https://www.cnblogs.com/wd-one/p/4539305.html
    
    int out[30], in[30], step[1005], pre[30];
    int n, k, t, st;
    char w[25];
    
    struct Edge//结构体:边, 存储了边的起点(首字符)和终点(尾字符),状态(是否走过)
    {
        int s, e, v;
        char c[25];
    }edge[1005];
    
    bool cmp(Edge x, Edge y)
    {
        return strcmp(x.c, y.c) < 0 ? true : false;
    }
    int find(int x)//查找其父节点
    {
        if(pre[x] != x)
            pre[x] = find(pre[x]);
        return pre[x];
    }
    int panduan()//判断是否图是连通的
    {
        int fx = find(edge[1].s);
        for(int i = 1; i <= 26; i++)
        {
            if(out[i] > 0 || in[i] > 0)
            {
                if(find(i) != fx)
                    return 0;
            }
        }
        return 1;
    }
    void path(int en)//查找路径
    {
        for(int i = 1; i <= n; i++)
        {
            if(edge[i].v == 0 && edge[i].s == en)
            {
                edge[i].v = 1;
                path(edge[i].e);
                step[++k] = i;
            }
        }
    }
    int main()
    {
        cin >> t;
        while(t--)
        {
            memset(out, 0, sizeof(out));
            memset(in, 0, sizeof(in));
            memset(step, 0, sizeof(step));
            for(int i = 1; i <= 30; i++)
                pre[i] = i;
            scanf("%d", &n);
            for(int i = 1; i <= n; i++)
            {
                cin >> w;
                int len = strlen(w);
                int s = w[0] - 'a' + 1;
                int e = w[len - 1] - 'a' + 1;
                edge[i].s = s;
                edge[i].e = e;
                strcpy(edge[i].c, w);
                edge[i].v = 0;
    
                out[s]++;
                in[e]++;
                /*如果存在欧拉路径,那么所有的点一定都连通.所有的点都在一个集合里,可以用并查集知识
                将所有连接的点并到一起。*/
                int fx = find(s);
                int fy = find(e);
                if(fx != fy)
                    pre[fx] = fy;
            }
            sort(edge + 1, edge + 1 + n, cmp);//题目要求字典序最小输出,就先按从小到大的顺序把边(单词)排好
            /*st代表的是路径起点,在这里进行st = edge[1].s赋值,是应为存在两种情况:1.存在一定点出度>入度,
            这个点是起点。2.所有点出度= 入度, 那么从任意一点出发都可以, 为了保证字典序最小, 就从第一个单词开始*/
            st = edge[1].s;
            int i, c1 = 0, c2 = 0;
            for(i = 1; i <= 26; i++)//判断是否有欧拉回路
            {
                if(out[i] == in[i])continue;
                else if(in[i] == out[i] - 1) {c1++; st = i;}//起点
                else if(in[i] == out[i] + 1) c2++;
                else break;
            }
            //如果符合了连通图,并且存在欧拉通路, 就开始找路径
            if(i == 27 && ((c1 == c2 && c1 == 1) || (c1 == c2 && c1 == 0)) && panduan() == 1)
            {
                k = 0;
                path(st);
                for(int i = n; i > 1; i--)//输出这注意点,因为是递归求的路径, 最先得到的是最后的边
                    printf("%s.", edge[step[i]].c);
                printf("%s
    ", edge[step[1]].c);
            }
            else
                printf("***
    ");
        }
        return 0;
    }
    View Code

        上面的代码并查集部分似乎不太必要,大概能加快连通图的判断,修改成自己的风格后依旧0ms:

    #include<iostream>
    #include<cstdio>
    #include<string.h>
    #include<cstring>
    #include<algorithm>
    using namespace std;
    // 参考自 https://www.cnblogs.com/wd-one/p/4539305.html
    int out[30], in[30], step[1005];
    int n, k, st;
    
    struct Edge
    {
        int s, e, vis;
        char c[30];
    }edge[1005];
    
    bool cmp(const Edge &x, const Edge &y)
    {
        return strcmp(x.c, y.c) < 0;
    }
    
    
    void path(int st)
    {
        for(int i = 1; i <= n; i++)
        {
            if(edge[i].vis == 0 && edge[i].s == st)
            {
                edge[i].vis = 1;
                path(edge[i].e);
                step[++k] = i;
            }
        }
    }
    int main()
    {
        int t; cin >> t;
        while(t--)
        {
            memset(out, 0, sizeof(out));
            memset(in, 0, sizeof(in));
            memset(step, 0, sizeof(step));
      
            scanf("%d", &n);
            for(int i = 1; i <= n; i++)
            {
                char w[25];
                scanf("%s", w);
                int len = strlen(w);
                int s = w[0] - 'a' + 1;
                int e = w[len - 1] - 'a' + 1;
                edge[i].s = s;
                edge[i].e = e;
                strcpy(edge[i].c, w);
                edge[i].vis = 0;
    
                out[s]++;
                in[e]++;
    
            }
            sort(edge + 1, edge + 1 + n, cmp);
      
    
            st = edge[1].s;
            int num = 0;
            bool can = true;
            for(int i=1;i<=26;i++) {
                if(out[i]-in[i]==1) {
                    ++num;
                    if(num==1) {
                        st = i;
                    } else {
                        can = false;
                        break;
                    }
                } else if(out[i]-in[i]>1 || in[i]-out[i]>1) {
                    can = false;
                    break;
                }
            }
           
            if(can)
            {
                k = 0;
                path(st); 
                if(k!=n)  {
                    printf("***
    ");
                } else {
                    for(int i = n; i>=1; i--)  {
                        printf("%s%c", edge[step[i]].c, i==1?'
    ':'.');
                    }
                }
                
            } else {
                printf("***
    ");
            }
        }
        return 0;
    }
    View Code

         (完)

  • 相关阅读:
    作业11 分类与监督学习,朴素贝叶斯分类算法
    第八次作业
    Python之路【第一篇】:Python基础1
    Python之路【第一篇】:Python基础
    Python 6 数字和布尔值及字符串的基本功能
    Python 7 列表 for 字典,嵌套
    Python 4 循环语句while
    Python 5 运算符
    Python 3 条件语句
    Python 2 声明变量 输入输出 练习
  • 原文地址:https://www.cnblogs.com/izcat/p/11148880.html
Copyright © 2011-2022 走看看