// 很久不写图论,连模板也不熟悉了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序列 (建图时按字典序排序后加入边)

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

  1. #include<cstdio>
  2. #include<iostream>
  3. #include<cstdlib>
  4. #include<string>
  5. #include<cstring>
  6. #include<vector>
  7. #include<algorithm>
  8.  
  9. using namespace std;
  10.  
  11. struct Edge {
  12. int to;
  13. int id; // 边的编号 letters[id] = letter
  14. bool vis; // dfs是否访问过
  15. // string letter;
  16. Edge(int v, int i): to(v), id(i) {
  17. vis = false;
  18. }
  19. };
  20. vector<Edge> edge[];
  21. string letters[];
  22. int in[], out[]; // 顶点的入度与出度
  23. int ans[], cnt;
  24.  
  25. void init() {
  26. cnt = ;
  27. memset(in, , sizeof(in));
  28. memset(out, , sizeof(out));
  29. for(int i=;i<;i++) {
  30. edge[i].clear();
  31. }
  32.  
  33. }
  34.  
  35. void dfs(char u) {
  36. for(int i=;i<edge[u].size();i++) {
  37. if(!edge[u][i].vis) {
  38. edge[u][i].vis = true;
  39. // cout<<edge[u][i].letter<<'-';
  40.  
  41. dfs(edge[u][i].to);
  42. ans[cnt++] = edge[u][i].id; // 这行是得到答案的关键,逆序存储字典序最小的路径
  43. }
  44. }
  45. }
  46.  
  47. int main()
  48. {
  49. int T, n;
  50. scanf("%d", &T);
  51. while(T--) {
  52. scanf("%d", &n);
  53.  
  54. init();
  55. int st = ;
  56. for(int i=;i<=n;i++) {
  57. char s[];
  58. scanf("%s", s);
  59. letters[i] = s;
  60. }
  61.  
  62. sort(letters+, letters++n);
  63.  
  64. for(int i=;i<=n;i++) {
  65. int u = letters[i][] - 'a';
  66. int v = *(letters[i].end()-) - 'a';
  67. edge[u].push_back(Edge(v, i));
  68. out[u]++;
  69. in[v]++;
  70. st = min(st, u);
  71. }
  72.  
  73. int num = ;
  74. bool can = true;
  75. for(int i=;i<;i++) {
  76. if(out[i]-in[i]==) {
  77. ++num;
  78. if(num==) {
  79. st = i;
  80. } else {
  81. can = false;
  82. break;
  83. }
  84. } else if(out[i]-in[i]> || in[i]-out[i]>) {
  85. can = false;
  86. break;
  87. }
  88. }
  89.  
  90. if(!can) {
  91. printf("***\n");
  92. continue;
  93. }
  94. // cout<<st<<endl;
  95. dfs(st);
  96. if(cnt!=n) {
  97. printf("***\n");
  98. continue;
  99. }
  100.  
  101. for(int i=cnt-;i>=;i--) {
  102. printf("%s%c", letters[ans[i]].c_str(), i!=?'.':'\n');
  103. }
  104.  
  105. }
  106. return ;
  107. }

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

  1. #include<iostream>
  2. #include<cstdio>
  3. #include<string.h>
  4. #include<cstring>
  5. #include<algorithm>
  6. using namespace std;
  7. // 来源:https://www.cnblogs.com/wd-one/p/4539305.html
  8.  
  9. int out[], in[], step[], pre[];
  10. int n, k, t, st;
  11. char w[];
  12.  
  13. struct Edge//结构体:边, 存储了边的起点(首字符)和终点(尾字符),状态(是否走过)
  14. {
  15. int s, e, v;
  16. char c[];
  17. }edge[];
  18.  
  19. bool cmp(Edge x, Edge y)
  20. {
  21. return strcmp(x.c, y.c) < ? true : false;
  22. }
  23. int find(int x)//查找其父节点
  24. {
  25. if(pre[x] != x)
  26. pre[x] = find(pre[x]);
  27. return pre[x];
  28. }
  29. int panduan()//判断是否图是连通的
  30. {
  31. int fx = find(edge[].s);
  32. for(int i = ; i <= ; i++)
  33. {
  34. if(out[i] > || in[i] > )
  35. {
  36. if(find(i) != fx)
  37. return ;
  38. }
  39. }
  40. return ;
  41. }
  42. void path(int en)//查找路径
  43. {
  44. for(int i = ; i <= n; i++)
  45. {
  46. if(edge[i].v == && edge[i].s == en)
  47. {
  48. edge[i].v = ;
  49. path(edge[i].e);
  50. step[++k] = i;
  51. }
  52. }
  53. }
  54. int main()
  55. {
  56. cin >> t;
  57. while(t--)
  58. {
  59. memset(out, , sizeof(out));
  60. memset(in, , sizeof(in));
  61. memset(step, , sizeof(step));
  62. for(int i = ; i <= ; i++)
  63. pre[i] = i;
  64. scanf("%d", &n);
  65. for(int i = ; i <= n; i++)
  66. {
  67. cin >> w;
  68. int len = strlen(w);
  69. int s = w[] - 'a' + ;
  70. int e = w[len - ] - 'a' + ;
  71. edge[i].s = s;
  72. edge[i].e = e;
  73. strcpy(edge[i].c, w);
  74. edge[i].v = ;
  75.  
  76. out[s]++;
  77. in[e]++;
  78. /*如果存在欧拉路径,那么所有的点一定都连通.所有的点都在一个集合里,可以用并查集知识
  79. 将所有连接的点并到一起。*/
  80. int fx = find(s);
  81. int fy = find(e);
  82. if(fx != fy)
  83. pre[fx] = fy;
  84. }
  85. sort(edge + , edge + + n, cmp);//题目要求字典序最小输出,就先按从小到大的顺序把边(单词)排好
  86. /*st代表的是路径起点,在这里进行st = edge[1].s赋值,是应为存在两种情况:1.存在一定点出度>入度,
  87. 这个点是起点。2.所有点出度= 入度, 那么从任意一点出发都可以, 为了保证字典序最小, 就从第一个单词开始*/
  88. st = edge[].s;
  89. int i, c1 = , c2 = ;
  90. for(i = ; i <= ; i++)//判断是否有欧拉回路
  91. {
  92. if(out[i] == in[i])continue;
  93. else if(in[i] == out[i] - ) {c1++; st = i;}//起点
  94. else if(in[i] == out[i] + ) c2++;
  95. else break;
  96. }
  97. //如果符合了连通图,并且存在欧拉通路, 就开始找路径
  98. if(i == && ((c1 == c2 && c1 == ) || (c1 == c2 && c1 == )) && panduan() == )
  99. {
  100. k = ;
  101. path(st);
  102. for(int i = n; i > ; i--)//输出这注意点,因为是递归求的路径, 最先得到的是最后的边
  103. printf("%s.", edge[step[i]].c);
  104. printf("%s\n", edge[step[]].c);
  105. }
  106. else
  107. printf("***\n");
  108. }
  109. return ;
  110. }

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

  1. #include<iostream>
  2. #include<cstdio>
  3. #include<string.h>
  4. #include<cstring>
  5. #include<algorithm>
  6. using namespace std;
  7. // 参考自 https://www.cnblogs.com/wd-one/p/4539305.html
  8. int out[], in[], step[];
  9. int n, k, st;
  10.  
  11. struct Edge
  12. {
  13. int s, e, vis;
  14. char c[];
  15. }edge[];
  16.  
  17. bool cmp(const Edge &x, const Edge &y)
  18. {
  19. return strcmp(x.c, y.c) < ;
  20. }
  21.  
  22. void path(int st)
  23. {
  24. for(int i = ; i <= n; i++)
  25. {
  26. if(edge[i].vis == && edge[i].s == st)
  27. {
  28. edge[i].vis = ;
  29. path(edge[i].e);
  30. step[++k] = i;
  31. }
  32. }
  33. }
  34. int main()
  35. {
  36. int t; cin >> t;
  37. while(t--)
  38. {
  39. memset(out, , sizeof(out));
  40. memset(in, , sizeof(in));
  41. memset(step, , sizeof(step));
  42.  
  43. scanf("%d", &n);
  44. for(int i = ; i <= n; i++)
  45. {
  46. char w[];
  47. scanf("%s", w);
  48. int len = strlen(w);
  49. int s = w[] - 'a' + ;
  50. int e = w[len - ] - 'a' + ;
  51. edge[i].s = s;
  52. edge[i].e = e;
  53. strcpy(edge[i].c, w);
  54. edge[i].vis = ;
  55.  
  56. out[s]++;
  57. in[e]++;
  58.  
  59. }
  60. sort(edge + , edge + + n, cmp);
  61.  
  62. st = edge[].s;
  63. int num = ;
  64. bool can = true;
  65. for(int i=;i<=;i++) {
  66. if(out[i]-in[i]==) {
  67. ++num;
  68. if(num==) {
  69. st = i;
  70. } else {
  71. can = false;
  72. break;
  73. }
  74. } else if(out[i]-in[i]> || in[i]-out[i]>) {
  75. can = false;
  76. break;
  77. }
  78. }
  79.  
  80. if(can)
  81. {
  82. k = ;
  83. path(st);
  84. if(k!=n) {
  85. printf("***\n");
  86. } else {
  87. for(int i = n; i>=; i--) {
  88. printf("%s%c", edge[step[i]].c, i==?'\n':'.');
  89. }
  90. }
  91.  
  92. } else {
  93. printf("***\n");
  94. }
  95. }
  96. return ;
  97. }

(完)

Catenyms (POJ2337) 字典序最小欧拉路的更多相关文章

  1. POJ 2337 Catenyms (有向图欧拉路径,求字典序最小的解)

    Catenyms Time Limit: 1000MS   Memory Limit: 65536K Total Submissions: 8756   Accepted: 2306 Descript ...

  2. [poj2337]求字典序最小欧拉回路

    注意:找出一条欧拉回路,与判定这个图能不能一笔联通...是不同的概念 c++奇怪的编译规则...生不如死啊... string怎么用啊...cincout来救? 可以直接.length()我也是长见识 ...

  3. 欧拉路&&欧拉回路 概念及其练习

    欧拉路: 如果给定无孤立结点图G,若存在一条路,经过图中每边一次且仅一次,这条路称为欧拉路: 如果给定无孤立结点图G,若存在一条回路,经过图中每边一次且仅一次,那么该回路称为欧拉回路. 存在欧拉回路的 ...

  4. 洛谷P1341 无序字母对[无向图欧拉路]

    题目描述 给定n个各不相同的无序字母对(区分大小写,无序即字母对中的两个字母可以位置颠倒).请构造一个有n+1个字母的字符串使得每个字母对都在这个字符串中出现. 输入输出格式 输入格式: 第一行输入一 ...

  5. 洛谷 P1341 无序字母对 Label:欧拉路 一笔画

    题目描述 给定n个各不相同的无序字母对(区分大小写,无序即字母对中的两个字母可以位置颠倒).请构造一个有n+1个字母的字符串使得每个字母对都在这个字符串中出现. 输入输出格式 输入格式: 第一行输入一 ...

  6. poj 1780 code(欧拉路)

    /* 对于n为密码想要序列最短 那么 1234 2345 这两个一定挨着 就是说 前一个的后n-1位是后一个的前n-1位 假设n==3 我们用0-99作为点的编号建图 然后每个点连出去10条边 两个相 ...

  7. poj 2337(单向欧拉路的判断以及输出)

    Catenyms Time Limit: 1000MS   Memory Limit: 65536K Total Submissions: 11648   Accepted: 3036 Descrip ...

  8. 欧拉路小结 JZYZOJ1210 骑马修栅栏

    现在写到欧拉路,理解起来并不算特别困难...吧... 但是非常恶心的是每次都要调试半天,做不到一遍两遍就能ac 每次写程序都对于程序的整体构架没有清晰的思路,想到哪里写到哪里真的是个非常差的习惯[尽管 ...

  9. CCF 201512-4 送货 (并查集+DFS,欧拉路)

    问题描述 为了增加公司收入,F公司新开设了物流业务.由于F公司在业界的良好口碑,物流业务一开通即受到了消费者的欢迎,物流业务马上遍及了城市的每条街道.然而,F公司现在只安排了小明一个人负责所有街道的服 ...

随机推荐

  1. Neo4j中实现自定义中文全文索引

    数据库检索效率时,一般首要优化途径是从索引入手,然后根据需求再考虑更复杂的负载均衡.读写分离和分布式水平/垂直分库/表等手段:索引通过信息冗余来提高检索效率,其以空间换时间并会降低数据写入的效率:因此 ...

  2. 搭建HDFS HA

    搭建HDFS HA 1.服务器角色规划 hd-01(192.168.1.99) hd-02 (192.168.1.100) hd-03 (192.168.1.101) NameNode NameNod ...

  3. vue cli3使用webpack4打包优化

    去掉console.log,以及开启gzip const CompressionPlugin = require('compression-webpack-plugin');//引入gzip压缩插件 ...

  4. django项目基础

    D:\>django-admin startproject GodWork1 D:\>cd GodWork1 D:\GodWork1>python manage.py startap ...

  5. list集合排序3

    java list按照元素对象的指定多个字段属性进行排序 转载 2016年12月27日 11:39:02 见: http://blog.csdn.net/enable1234___/article/d ...

  6. c# 使用Expression 生成sql

    使用Expression 生成sql  update语句的时候遇到了个问题 ,Expression<Action<T>>  la   这个委托里面老获取不到 引用类型的值,甚至 ...

  7. Liunx下安装Oracle11g时Oracle Grid安装包下载向导

    下载Oracel 11g  Grid的安装包 Oracle官网 https://www.oracle.com 快捷访问路径:https://www.oracle.com/technetwork/dat ...

  8. 宝塔面板安装swoole扩展

    Swoole是一个PHP扩展,扩展不是为了提升网站的性能,是为了提升网站的开发效率.最少的性能损耗,换取最大的开发效率.利用Swoole扩展,开发一个复杂的Web功能,可以在很短的时间内完成 Swoo ...

  9. 廖雪峰Java16函数式编程-2Stream-5filter

    1.filter简介 Stream.filter()是一个转换方法,把一个Stream转换为另一个Stream. 所谓filter操作,就是对一个Stream的所有元素进行测试,不满足条件的元素就被过 ...

  10. 修改docker+jenkins挂载目录

    1.停止docker [root@jenkins data]# systemctl stop docker 2.创建目录,拷贝数据 [root@jenkins data]# mkdir -p /new ...