洛谷 P1341 无序字母对

【题目链接】

洛谷 P1341 无序字母对

【题目考点】

1. 欧拉图:欧拉路径(无向图)

【解题思路】

每个字母是一个顶点,每个字母对是一条边。“无序即字母对中的两个字母可以位置颠倒”,意味着这样的边连接的两个字母没有先后顺序,是无向边,形成的图是无向图。
给定n个不同的字母对,也就是n个不同的边(没有重边),要得到一个有n+1个字母的字符串,每个字母对都出现。相邻两个字母是一个字母对,n+1个字母的字符串中可以出现n个字母对,也就是说给定的n个字母对都要出现。

该字符串相当于图中的一条路径,字符串中相邻的字母构成的字母对是一条边。要想让所有字母对都出现,也就是该路径要经过图中所有的边,即该路径是欧拉路径。

该问题抽象为:每个字母是一个顶点,每个字母对是一条边,求该无向图中的欧拉路径。

大写与小写字母的ASCII码在0~127范围内,可以直接用字母的ASCII码作为该字母对应顶点的编号。
判断无向图有欧拉路径的条件

  • 所有顶点都是偶数度
  • 只有两个顶点是奇数度,其它顶点是偶数度

输入边的同时,统计顶点的度。
遍历所有顶点,统计度为奇数的顶点数。
由于要求输出字典序最小的方案,因此必须选择可能作为起点的编号最小的顶点。

  • 如果所有顶点都是偶数度,那么按编号从小到大遍历所有顶点,选择第一个度大于0的顶点作为起点
  • 如果存在两个度为奇数的顶点,那么按编号从小到大遍历所有顶点,选择第一个奇数度顶点作为起点
  • 对于其它情况,该图不存在欧拉路径,输出"No Solution"

从起点出发,调用Hierholzer算法,求出欧拉路径,最后把欧拉路径转化成字符串输出。

【题解代码】

写法1:使用邻接矩阵
#include <bits/stdc++.h>
using namespace std;
#define N 128
int m, n = 128, edge[N][N], deg[N];
stack<char> stk;
void dfs(int u)
{
	for(int v = 0; v < n; ++v)
	{
		if(edge[u][v])
		{
			edge[u][v] = edge[v][u] = 0;
			dfs(v); 
		}
	}
	stk.push(u);
} 
int main()
{
	int oddNum = 0;//奇数度顶点数量 
	char f, t, st;
	cin >> m;
	for(int i = 1; i <= m; ++i)
	{
		cin >> f >> t;//scanf("%c%c\n", &f, &t);
		edge[f][t] = edge[t][f] = 1;//无重边 
		deg[f]++, deg[t]++;
	}
	for(int v = 0; v < n; ++v)
		if(deg[v] > 0 && deg[v]%2 == 1)
			oddNum++;
	if(oddNum == 0)//有欧拉回路 
	{
		for(int v = 0; v < n; ++v)
			if(deg[v] > 0)
			{
				st = v;
				break;
			}
	}
	else if(oddNum == 2)//有欧拉路径
	{
		for(int v = 0; v < n; ++v)
			if(deg[v]%2 == 1)
			{
				st = v;
				break;
			}
	}
	else
	{
		cout << "No Solution";
		return 0;
	}
	dfs(st);
	while(stk.empty() == false)
	{
		cout << stk.top();
		stk.pop();
	}
	return 0;
}
写法2:使用邻接表
#include<bits/stdc++.h>
using namespace std;
#define N 130
#define M 1330 //边最大数量,为52个结点中选两个连边的数量:C(52, 2) = 1326
struct Node
{
	int v, e;//v:顶点编号 e:边编号 
	Node(){}
	Node(int a, int b):v(a), e(b){}
	bool operator < (Node b)
	{
		return v < b.v;
	}
};
int n, deg[N], beg[N];//beg[i]:edge[i]从下标beg[i]开始,下标更前面的顶点相当于已被删掉
vector<Node> edge[N];
bool vis[M]; 
stack<char> stk;
void dfs(int u)
{
	for(int &i = beg[u]; i < edge[u].size(); ++i)
	{
		int v = edge[u][i].v, e = edge[u][i].e;
		if(vis[e] == false)
		{
			vis[e] = true;
			dfs(v);
		}
	}
	stk.push(u);
}
int main()
{
	char f, t, st;
	int oddNum = 0;//奇数度顶点数量 
	cin >> n;
	for(int i = 1; i <= n; ++i)
	{
		cin >> f >> t;
		edge[f].push_back(Node(t, i));
		edge[t].push_back(Node(f, i));
		deg[f]++, deg[t]++;
	}
	for(int i = 0; i < 128; ++i)
		sort(edge[i].begin(), edge[i].end());//使每个vector有序,以输出字典序最小的序列。
	for(int v = 0; v < 128; ++v)
		if(deg[v]%2 == 1)
			oddNum++;
	if(oddNum == 0)
	{
		for(int v = 0; v < 128; ++v)
			if(deg[v] > 0)
			{
				st = v;
				break;
			}
	}
	else if(oddNum == 2)
	{
		for(int v = 0; v < 128; ++v)
			if(deg[v]%2 == 1)
			{
				st = v;
				break;
			}
	}
	else
	{
		cout << "No Solution";
		return 0;
	}
	dfs(st);
	while(stk.empty() == false)
	{
		cout << stk.top();
		stk.pop();
	}
	return 0;
}
  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值