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[];
- string letters[];
- int in[], out[]; // 顶点的入度与出度
- int ans[], cnt;
- void init() {
- cnt = ;
- memset(in, , sizeof(in));
- memset(out, , sizeof(out));
- for(int i=;i<;i++) {
- edge[i].clear();
- }
- }
- void dfs(char u) {
- for(int i=;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 = ;
- for(int i=;i<=n;i++) {
- char s[];
- scanf("%s", s);
- letters[i] = s;
- }
- sort(letters+, letters++n);
- for(int i=;i<=n;i++) {
- int u = letters[i][] - 'a';
- int v = *(letters[i].end()-) - 'a';
- edge[u].push_back(Edge(v, i));
- out[u]++;
- in[v]++;
- st = min(st, u);
- }
- int num = ;
- bool can = true;
- for(int i=;i<;i++) {
- if(out[i]-in[i]==) {
- ++num;
- if(num==) {
- st = i;
- } else {
- can = false;
- break;
- }
- } else if(out[i]-in[i]> || in[i]-out[i]>) {
- can = false;
- break;
- }
- }
- if(!can) {
- printf("***\n");
- continue;
- }
- // cout<<st<<endl;
- dfs(st);
- if(cnt!=n) {
- printf("***\n");
- continue;
- }
- for(int i=cnt-;i>=;i--) {
- printf("%s%c", letters[ans[i]].c_str(), i!=?'.':'\n');
- }
- }
- return ;
- }
参考别人没有建图的代码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[], in[], step[], pre[];
- int n, k, t, st;
- char w[];
- struct Edge//结构体:边, 存储了边的起点(首字符)和终点(尾字符),状态(是否走过)
- {
- int s, e, v;
- char c[];
- }edge[];
- bool cmp(Edge x, Edge y)
- {
- return strcmp(x.c, y.c) < ? true : false;
- }
- int find(int x)//查找其父节点
- {
- if(pre[x] != x)
- pre[x] = find(pre[x]);
- return pre[x];
- }
- int panduan()//判断是否图是连通的
- {
- int fx = find(edge[].s);
- for(int i = ; i <= ; i++)
- {
- if(out[i] > || in[i] > )
- {
- if(find(i) != fx)
- return ;
- }
- }
- return ;
- }
- void path(int en)//查找路径
- {
- for(int i = ; i <= n; i++)
- {
- if(edge[i].v == && edge[i].s == en)
- {
- edge[i].v = ;
- path(edge[i].e);
- step[++k] = i;
- }
- }
- }
- int main()
- {
- cin >> t;
- while(t--)
- {
- memset(out, , sizeof(out));
- memset(in, , sizeof(in));
- memset(step, , sizeof(step));
- for(int i = ; i <= ; i++)
- pre[i] = i;
- scanf("%d", &n);
- for(int i = ; i <= n; i++)
- {
- cin >> w;
- int len = strlen(w);
- int s = w[] - 'a' + ;
- int e = w[len - ] - 'a' + ;
- edge[i].s = s;
- edge[i].e = e;
- strcpy(edge[i].c, w);
- edge[i].v = ;
- out[s]++;
- in[e]++;
- /*如果存在欧拉路径,那么所有的点一定都连通.所有的点都在一个集合里,可以用并查集知识
- 将所有连接的点并到一起。*/
- int fx = find(s);
- int fy = find(e);
- if(fx != fy)
- pre[fx] = fy;
- }
- sort(edge + , edge + + n, cmp);//题目要求字典序最小输出,就先按从小到大的顺序把边(单词)排好
- /*st代表的是路径起点,在这里进行st = edge[1].s赋值,是应为存在两种情况:1.存在一定点出度>入度,
- 这个点是起点。2.所有点出度= 入度, 那么从任意一点出发都可以, 为了保证字典序最小, 就从第一个单词开始*/
- st = edge[].s;
- int i, c1 = , c2 = ;
- for(i = ; i <= ; i++)//判断是否有欧拉回路
- {
- if(out[i] == in[i])continue;
- else if(in[i] == out[i] - ) {c1++; st = i;}//起点
- else if(in[i] == out[i] + ) c2++;
- else break;
- }
- //如果符合了连通图,并且存在欧拉通路, 就开始找路径
- if(i == && ((c1 == c2 && c1 == ) || (c1 == c2 && c1 == )) && panduan() == )
- {
- k = ;
- path(st);
- for(int i = n; i > ; i--)//输出这注意点,因为是递归求的路径, 最先得到的是最后的边
- printf("%s.", edge[step[i]].c);
- printf("%s\n", edge[step[]].c);
- }
- else
- printf("***\n");
- }
- return ;
- }
上面的代码并查集部分似乎不太必要,大概能加快连通图的判断,修改成自己的风格后依旧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[], in[], step[];
- int n, k, st;
- struct Edge
- {
- int s, e, vis;
- char c[];
- }edge[];
- bool cmp(const Edge &x, const Edge &y)
- {
- return strcmp(x.c, y.c) < ;
- }
- void path(int st)
- {
- for(int i = ; i <= n; i++)
- {
- if(edge[i].vis == && edge[i].s == st)
- {
- edge[i].vis = ;
- path(edge[i].e);
- step[++k] = i;
- }
- }
- }
- int main()
- {
- int t; cin >> t;
- while(t--)
- {
- memset(out, , sizeof(out));
- memset(in, , sizeof(in));
- memset(step, , sizeof(step));
- scanf("%d", &n);
- for(int i = ; i <= n; i++)
- {
- char w[];
- scanf("%s", w);
- int len = strlen(w);
- int s = w[] - 'a' + ;
- int e = w[len - ] - 'a' + ;
- edge[i].s = s;
- edge[i].e = e;
- strcpy(edge[i].c, w);
- edge[i].vis = ;
- out[s]++;
- in[e]++;
- }
- sort(edge + , edge + + n, cmp);
- st = edge[].s;
- int num = ;
- bool can = true;
- for(int i=;i<=;i++) {
- if(out[i]-in[i]==) {
- ++num;
- if(num==) {
- st = i;
- } else {
- can = false;
- break;
- }
- } else if(out[i]-in[i]> || in[i]-out[i]>) {
- can = false;
- break;
- }
- }
- if(can)
- {
- k = ;
- path(st);
- if(k!=n) {
- printf("***\n");
- } else {
- for(int i = n; i>=; i--) {
- printf("%s%c", edge[step[i]].c, i==?'\n':'.');
- }
- }
- } else {
- printf("***\n");
- }
- }
- return ;
- }
(完)
Catenyms (POJ2337) 字典序最小欧拉路的更多相关文章
- POJ 2337 Catenyms (有向图欧拉路径,求字典序最小的解)
Catenyms Time Limit: 1000MS Memory Limit: 65536K Total Submissions: 8756 Accepted: 2306 Descript ...
- [poj2337]求字典序最小欧拉回路
注意:找出一条欧拉回路,与判定这个图能不能一笔联通...是不同的概念 c++奇怪的编译规则...生不如死啊... string怎么用啊...cincout来救? 可以直接.length()我也是长见识 ...
- 欧拉路&&欧拉回路 概念及其练习
欧拉路: 如果给定无孤立结点图G,若存在一条路,经过图中每边一次且仅一次,这条路称为欧拉路: 如果给定无孤立结点图G,若存在一条回路,经过图中每边一次且仅一次,那么该回路称为欧拉回路. 存在欧拉回路的 ...
- 洛谷P1341 无序字母对[无向图欧拉路]
题目描述 给定n个各不相同的无序字母对(区分大小写,无序即字母对中的两个字母可以位置颠倒).请构造一个有n+1个字母的字符串使得每个字母对都在这个字符串中出现. 输入输出格式 输入格式: 第一行输入一 ...
- 洛谷 P1341 无序字母对 Label:欧拉路 一笔画
题目描述 给定n个各不相同的无序字母对(区分大小写,无序即字母对中的两个字母可以位置颠倒).请构造一个有n+1个字母的字符串使得每个字母对都在这个字符串中出现. 输入输出格式 输入格式: 第一行输入一 ...
- poj 1780 code(欧拉路)
/* 对于n为密码想要序列最短 那么 1234 2345 这两个一定挨着 就是说 前一个的后n-1位是后一个的前n-1位 假设n==3 我们用0-99作为点的编号建图 然后每个点连出去10条边 两个相 ...
- poj 2337(单向欧拉路的判断以及输出)
Catenyms Time Limit: 1000MS Memory Limit: 65536K Total Submissions: 11648 Accepted: 3036 Descrip ...
- 欧拉路小结 JZYZOJ1210 骑马修栅栏
现在写到欧拉路,理解起来并不算特别困难...吧... 但是非常恶心的是每次都要调试半天,做不到一遍两遍就能ac 每次写程序都对于程序的整体构架没有清晰的思路,想到哪里写到哪里真的是个非常差的习惯[尽管 ...
- CCF 201512-4 送货 (并查集+DFS,欧拉路)
问题描述 为了增加公司收入,F公司新开设了物流业务.由于F公司在业界的良好口碑,物流业务一开通即受到了消费者的欢迎,物流业务马上遍及了城市的每条街道.然而,F公司现在只安排了小明一个人负责所有街道的服 ...
随机推荐
- Neo4j中实现自定义中文全文索引
数据库检索效率时,一般首要优化途径是从索引入手,然后根据需求再考虑更复杂的负载均衡.读写分离和分布式水平/垂直分库/表等手段:索引通过信息冗余来提高检索效率,其以空间换时间并会降低数据写入的效率:因此 ...
- 搭建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 ...
- vue cli3使用webpack4打包优化
去掉console.log,以及开启gzip const CompressionPlugin = require('compression-webpack-plugin');//引入gzip压缩插件 ...
- django项目基础
D:\>django-admin startproject GodWork1 D:\>cd GodWork1 D:\GodWork1>python manage.py startap ...
- list集合排序3
java list按照元素对象的指定多个字段属性进行排序 转载 2016年12月27日 11:39:02 见: http://blog.csdn.net/enable1234___/article/d ...
- c# 使用Expression 生成sql
使用Expression 生成sql update语句的时候遇到了个问题 ,Expression<Action<T>> la 这个委托里面老获取不到 引用类型的值,甚至 ...
- Liunx下安装Oracle11g时Oracle Grid安装包下载向导
下载Oracel 11g Grid的安装包 Oracle官网 https://www.oracle.com 快捷访问路径:https://www.oracle.com/technetwork/dat ...
- 宝塔面板安装swoole扩展
Swoole是一个PHP扩展,扩展不是为了提升网站的性能,是为了提升网站的开发效率.最少的性能损耗,换取最大的开发效率.利用Swoole扩展,开发一个复杂的Web功能,可以在很短的时间内完成 Swoo ...
- 廖雪峰Java16函数式编程-2Stream-5filter
1.filter简介 Stream.filter()是一个转换方法,把一个Stream转换为另一个Stream. 所谓filter操作,就是对一个Stream的所有元素进行测试,不满足条件的元素就被过 ...
- 修改docker+jenkins挂载目录
1.停止docker [root@jenkins data]# systemctl stop docker 2.创建目录,拷贝数据 [root@jenkins data]# mkdir -p /new ...