杭电1116————欧拉回路(通路) + 并查集基础

Play on Words

Time Limit: 10000/5000 MS (Java/Others)    Memory Limit: 65536/32768 K (Java/Others)
Total Submission(s): 5338    Accepted Submission(s): 1733


Problem Description
Some of the secret doors contain a very interesting word puzzle. The team of archaeologists has to solve it to open that doors. Because there is no other way to open the doors, the puzzle is very important for us. 

There is a large number of magnetic plates on every door. Every plate has one word written on it. The plates must be arranged into a sequence in such a way that every word begins with the same letter as the previous word ends. For example, the word ``acm'' can be followed by the word ``motorola''. Your task is to write a computer program that will read the list of words and determine whether it is possible to arrange all of the plates in a sequence (according to the given rule) and consequently to open the door. 
 

Input
The input consists of T test cases. The number of them (T) is given on the first line of the input file. Each test case begins with a line containing a single integer number Nthat indicates the number of plates (1 <= N <= 100000). Then exactly Nlines follow, each containing a single word. Each word contains at least two and at most 1000 lowercase characters, that means only letters 'a' through 'z' will appear in the word. The same word may appear several times in the list. 
 

Output
Your program has to determine whether it is possible to arrange all the plates in a sequence such that the first letter of each word is equal to the last letter of the previous word. All the plates from the list must be used, each exactly once. The words mentioned several times must be used that number of times. 
If there exists such an ordering of plates, your program should print the sentence "Ordering is possible.". Otherwise, output the sentence "The door cannot be opened.". 
 

Sample Input
  
  
3 2 acm ibm 3 acm malform mouse 2 ok ok
 

Sample Output
  
  
The door cannot be opened. Ordering is possible. The door cannot be opened.
新学的东西,并查集....其实还不怎么懂...刚好梳理一下。
刚看到这个题目的时候,虽然我知道它是关于欧拉回路的题目..但是我想有没有以前我学过的办法。
我第一个出来的思路是DFS,应该是最简单的思路了吧?然而我又看了一眼数据规模我就放弃了...10W深搜得哪年。
后来我又想..这是一个有向图的欧拉回路(通路)。可是为了用欧拉回路的判别方法,我必须把这些单词啊什么的抽象成一个图才可以。
    
    
回忆下欧拉回路(通路)的判断方法
欧拉通路:

一。无向图 一个无向图存在欧拉通路,当且仅当   该图所有顶点的度数为偶数   或者  除了两个度数为奇数外其余的全是偶数

二。有向图 一个有向图存在欧拉通路,当且仅当  除了初始节点和最末尾结点外(初始结点入度为0,末尾结点出度为0)该图所有顶点出度入度相等   

欧拉回路:

一、无向图 每个顶点的度数都是偶数,则存在欧拉回路。

二、有向图(所有边都是单向的) 每个节顶点的入度都等于出度,则存在欧拉回路。

abstract_graphy[i][j]表示如果第i个单词和第j个单词之间可以首尾相接就为1
这是我最初的想法,不过想起来10W的数据规模我只能默默的跪了...
百度一下,发现这是一个利用并查集数据结构的题目。(刚刚接触..)
目前知道的并查集在图论中的应用就是判断图的连通性(稍后结合例子说一下就明白了)

那么整个题目的思路就很流畅了..

1.并查集判断连通

2.将每个单词取出首字母和尾字母,转换为一条边,然后加入对应的连通分量中。如果这个字母出现过,visit数组标记为true。同时起点出度加1,终点入度加1.

3.判断:

1)这个图必须是连通的,即根结点只有一个。如果不是,直接结束本次算法。

2)如果这个图是连通的,判断每个结点的入度和出度情况。

     如果这个图是欧拉图(欧拉回路),则每个顶点的出度等于入度。即out[i] = in[i]

如果这个图是半欧拉图(即只有欧拉通路),则起点的出度比入度大1,终点的入度比出度大1.其余顶点的出度等于入度。


先贴上代码,然后用例子说明一下并查集算法(其实也是一种数据结果吧)
/*亲测203MS*/
#include <stdio.h>
#include <string.h>
#define maxn 1005
/*查并集就是查找合并集合*/ 
int indegree[27],outdegree[27];//出度入度 
int in,out,root;//root是根节点的个数,如果根节点>1那么就是不连通的 
bool vis[27],is_adjacent,flag;/*如果字母i出现过vis就标记为true*/ 
int set[27];/*这个set就是所利用的并查集*/ 
char s[maxn];
void initialize()
{
	memset(indegree,0,sizeof(indegree));
	memset(outdegree,0,sizeof(outdegree));
	memset(vis,false,sizeof(vis));
	for(int i = 1 ; i <= 26 ;i++)
		set[i] = i;
	/*并查集的初始化set[i],默认i的根节点就是i,即set[i] = i*/
	in = 0;
	out = 0;
	root = 0;
	is_adjacent = true;/*默认为连通的*/ 
	flag = true;
}
int find_root(int x)
{
	return  (x == set[x]) ? x : find_root(set[x]);
	/*当x != set[x]时,说明了set[x]不是x的根节点,继续向上回溯,find_root(set[x]),set[x]
	此时是x的父亲结点但不是根节点*/ 
}
void Union(int x,int y)
{
	int root1 = find_root(x);/*find_root即查找集合*/ 
	int root2 = find_root(y);
	if(root1 != root2)/*根节点不一样说明是两个集合,合并集合,Union*/ 
		set[root1] = root2;	
	/*set[root2] = root1 的意思就是root2的父亲是root1,顺序其实无所谓*/ 
	/*这个地方正反无所谓*/ 
	/*如果这两个点的根节点一样的话...为什么不作处理?*/ 
}
int main()
{
	int T,N;
	
	scanf("%d",&T);
	while(T--)
	{
		scanf("%d",&N);
		initialize();
		for(int i = 1 ; i<= N ; i++)
		{
			scanf("%s",s);
			int len =strlen(s);
			int start = s[0] - 'a' + 1;
			int end = s[len - 1] - 'a' + 1;
			vis[start] = true;
			vis[end] = true;
			indegree[start]++;
			outdegree[end]++;
			Union(start,end);/*将一个单词的首尾提取出来,抽象成一条边*/
		}
		for(int i = 1; i <= 26; i++)
		{		
			if(vis[i])/*该字母出现过*/ 
			{
				if(set[i] == i)
					root++;/*set[i] == i 时这是一个根节点*/ 
				if(indegree[i] != outdegree[i])/*该字母的出入度不相等时*/ 
				{
					if(indegree[i] - outdegree[i] == 1)
						in++;
					else if(outdegree[i] - indegree[i] == 1)
						out++;
					else/*字母的出入度既不相等,之差也不是1,此时不能构成欧拉回路通路*/ 
						flag = false;
				}
			}
			if(root > 1)/*不止一个根节点就是说有好多连通分量,即整个图不是连通的*/ 
			{
				is_adjacent = false;
				break;
			}
		}
		/*if欧拉回路 或者 if 欧拉通路*/ 
		if((is_adjacent && in == 0 && out == 0 && flag) || (is_adjacent && in == 1 && out == 1 && flag))
			printf("Ordering is possible.\n");
		else
			printf("The door cannot be opened.\n");
	}
	return 0;
}</span>
以上这段程序中,可以说最重要的就是
Union和find_root这两个函数.
void Union(int x,int y)
{
	int root1 = find_root(x);/*find_root即查找集合*/ 
	int root2 = find_root(y);
	if(root1 != root2)/*根节点不一样说明是两个集合,合并集合,Union*/ 
		set[root1] = root2;	
	/*set[root2] = root1 的意思就是root2的父亲是root1,顺序其实无所谓*/ 
	/*这个地方正反无所谓*/ 
	/*如果这两个点的根节点一样的话...为什么不作处理?*/ 
}</span>

Union就是合并两个集合的操作,其实就这样构建了一棵树。把一棵树和另一棵树合并,就让一棵树的根节点指向另一棵树的根节点即可,也就是
set[root2] = root1 这一步
int find_root(int x)
{
	return  (x == set[x]) ? x : find_root(set[x]);
	/*当x != set[x]时,说明了set[x]不是x的根节点,继续向上回溯,find_root(set[x]),set[x]
	此时是x的父亲结点但不是根节点*/ 
}</span>
这个函数就是查找元素x的所在的那棵树的根节点。
因为在初始化的时候,我们设定了x==set[x]时就是根节点,如果x != set[x] set[x]点是x的一个父亲节点,对它继续回溯即可找到根节点。

下面拿例子详细说一下:(自己多演算一下就明白了)



 


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值