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公司现在只安排了小明一个人负责所有街道的服 ...
随机推荐
- oracle union 和 union all
java.sql.SQLSyntaxErrorException: ORA-01789: 查询块具有不正确的结果列数 原因: 发现是sql语句用union时的 两个语句查询的字段不一致 解决:将 2个 ...
- OpenLiveWriter博客工具
1.OpenLiveWriter安装 官网下载地址:http://openlivewriter.org/ 默认安装到:C:\Users\用户\AppData\Local\OpenLiveWriter目 ...
- java获得磁盘、网络实时I/O速率
最近项目中需要一个平台硬件资源的监控模块,当时采用了Sigar中api,但是做到针对磁盘和网络的实时I/O速率的时候发现Sigar并没有直接支持的接口.于是……它就诞生了.底层采用C++编写,通过ja ...
- iOS开发系列-Category
Category Category是OC中特有的语法.Category的作用 * 可以在不修改原来类的基础上,为这个类扩充一些方法 * 一个庞大的类可以分为多个模块开发 * 一个庞大的类可以由多个人来 ...
- 【转】WebResource实现在自定义控件中内嵌JS文件
在类库中的资源 其他项目中要使用 需要嵌入才行 参考文献:WebResource实现在自定义控件中内嵌JS文件 1. WebResource简介 ASP.NET(1.0/1.1)给我们提供了一个开发 ...
- Qt Creator编译时提示找不到“ui_xxx.h”文件
解决方案: 在对应工程的*.pro文件里加上: QT+= widgets 则在编译过程中对应的“xxx.ui”文件会自动生成“ui_xxx.h”文件.
- [JZOJ3235] 数字八
题目 题目大意 给你一个二维的图,其中.代表完好,*代表有缺陷. 现在要在图上刻一个数字\(8\),满足: 由两个矩形组成. 每个矩形中必须有空隙在内部,也就是说,至少为\(3*3\)的矩形. 上矩形 ...
- 学习js和jQuery总结
数据类型的转换:1.字符串必须放在引号内2.字符串的转换有两个标签,string和tostring这两个函数.3.变量必须使用驼峰形式,把一个变量转换成字符串:console.log(isChild. ...
- Http学习(一)
HTTP 超文本传输协议 综述: HTTP(HyperText Transfer Protocol)是一套计算机通过网络进行通信的规则.计算机专家设计出HTTP,使HTTP客户(如Web浏览器)能够从 ...
- DELPHI指针的使用
DELPHI指针的使用 大家都认为,C语言之所以强大,以及其自由性,很大部分体现在其灵活的指针运用上.因此,说指针是C语言的灵魂,一点都不为过.同时,这种说法也让很多人产生误解,似乎只有C语言的指针才 ...