hdu1116 Play on Words--并查集&网络流

原题链接: http://acm.hdu.edu.cn/showproblem.php?pid=1116


一:原题内容

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.

二:分析理解

题目大意:给你一些英文单词,判断所有单词能不能连成一串,类似成语接龙的意思。但是如果有多个重复的单词时,也必须满足这样的条件才能算YES。否则都是不可能的情况。
解题思路:欧拉路的基本题。只要知道就可以做出来了。

关于欧拉回路和欧拉路径
定义:
欧拉回路:每条边恰好只走一次,并能回到出发点的路径
欧拉路径:经过每一条边一次,但是不要求回到起始点


①首先看欧拉回路存在性的判定:


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


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


 三.混合图欧拉回路
混合图欧拉回路用的是网络流。
把该图的无向边随便定向,计算每个点的入度和出度。如果有某个点出入度之差为奇数,那么肯定不存在欧拉回路。因为欧拉回路要求每点入度 = 出度,也就是总度数为偶数,存在奇数度点必不能有欧拉回路。
好了,现在每个点入度和出度之差均为偶数。那么将这个偶数除以2,得x。也就是说,对于每一个点,只要将x条边改变方向(入>出就是变入,出>入就是变出),就能保证出 = 入。如果每个点都是出 = 入,那么很明显,该图就存在欧拉回路。
现在的问题就变成了:我该改变哪些边,可以让每个点出 = 入?构造网络流模型。首先,有向边是不能改变方向的,要之无用,删。一开始不是把无向边定向了吗?定的是什么向,就把网络构建成什么样,边长容量上限1。另新建s和t。对于入 > 出的点u,连接边(u, t)、容量为x,对于出 > 入的点v,连接边(s, v),容量为x(注意对不同的点x不同)。之后,察看是否有满流的分配。有就是能有欧拉回路,没有就是没有。欧拉回路是哪个?查看流值分配,将所有流量非 0(上限是1,流值不是0就是1)的边反向,就能得到每点入度 =
出度的欧拉图。
由于是满流,所以每个入 > 出的点,都有x条边进来,将这些进来的边反向,OK,入 = 出了。对于出 > 入的点亦然。那么,没和s、t连接的点怎么办?和s连接的条件是出 > 入,和t连接的条件是入 > 出,那么这个既没和s也没和t连接的点,自然早在开始就已经满足入 = 出了。那么在网络流过程中,这些点属于“中间点”。我们知道中间点流量不允许有累积的,这样,进去多少就出来多少,反向之后,自然仍保持平衡。
所以,就这样,混合图欧拉回路问题,解了。


②.欧拉路径存在性的判定


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


二。有向图
一个有向图存在欧拉路径,当且仅当 该图所有顶点的度数为零     或者 一个顶点的度数为1,另一个度数为-1,其他顶点的度数为0。


三。混合图欧拉路径
其实整篇文章只有这部分是我写的哈,灰常不好意思,只是网上的同志们写的太好了,实在没有必要重复劳动,不知道大家有没有发现,求欧拉路径的第一步一定是求欧拉回路,在混合图上也不例外,如何判断混合图欧拉回路问题的存在性呢?首先,我们用上文所说的方法判断该图是否存在欧拉回路,如果存在,欧拉路径一定存在。如果欧拉回路不存在,那么我们枚举欧拉路径的起点和终点,连接一条无向边,然后再用最大流判断是否存在欧拉回路即可。


所以这道题的大题思路就是:
1.并查集判断连通
2.将每个单词取出首字母和尾字母,转换为一条边,然后加入对应的连通分量中。如果这个字母出现过,visit数组标记为true。同时起点出度加1,终点入度加1.
3.判断一下:


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

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

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

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

如果满足上述条件,就可以将所有单词链接起来,否则不能。

当然,在判断出度入度的时候还有一点需要注意,那就是除了起点终点以外的顶点,出度必须等于入度(出度入度可以同时为2,即环),但是起点和终点必须保证出度和入度之差为1。


三:AC代码

#define _CRT_SECURE_NO_DEPRECATE   
#define _CRT_SECURE_CPP_OVERLOAD_STANDARD_NAMES 1  

#include<iostream> 
#include<string.h>

using namespace std;

char ch[1001];    //存储单词
int pre[27];      //一共26个英文字母,下标从1开始,用于并查集
int in[27];       //表示每个单词的尾部字母的入度,默认是0
int out[27];      //表示每个单词的头部字母的出度,默认是0
bool visited[27]; //字母是否出现,默认是false
int T;            //测试实例个数
int N;            //每个实例要输入的单词的个数
int inNum;        //判断出现的所有的字母,“入度减出度为1”的个数
int outNum;       //判断出现的所有的字母,“出度减入度为1”的个数
int root;         //根节点个数
bool flag;        //判断出现的所有的字母的出度和入度之差是否是0或1,如果不是,那肯定不满足题意,还有判断root是否大于1,大于1的话,肯定不满足
int start;
int last;

int Find(int x)
{
	return (x != pre[x]) ? Find(pre[x]) : x;
}

void Union(int x, int y)
{
	int root_x = Find(x);
	int root_y = Find(y);

	if (root_x != root_y)
		pre[root_x] = root_y;
}

int main()
{
	cin >> T;
	while (T--)
	{
		for (int i = 1; i < 27; i++)
			pre[i] = i;
		memset(in, 0, sizeof(in));
		memset(out, 0, sizeof(out));
		memset(visited, 0, sizeof(visited));

		inNum = outNum = root = 0;
		flag = true;

		cin >> N;
		for (int i = 0; i < N; i++)
		{
			cin >> ch;
			int len = strlen(ch);

			start = ch[0] - 'a' + 1;
			last = ch[len - 1] - 'a' + 1;

			visited[start] = true;
			visited[last] = true;
			out[start]++;
			in[last]++;

			Union(start, last);
		}

		for (int i = 1; i <= 26; i++)
		{
			if (visited[i])
			{
				if (pre[i] == i)
					root++;
				if (out[i] != in[i])//注意点
				{
					if (out[i] - in[i] == 1)
						outNum++;
					else if (in[i] - out[i] == 1)
						inNum++;
					else
						flag = false;
				}
			}

			if (!flag)
				break;
			if (root > 1)
			{
				flag = false;
				break;
			}
		}

		if ((flag&&inNum == 0 && outNum == 0) || (flag&&inNum == 1 && outNum == 1))
			cout << "Ordering is possible.\n";
		else
			cout << "The door cannot be opened.\n";
	}

	return 0;
}





参考 :http://www.acmerblog.com/hdu-1116-play-on-words-1412.html

你可能感兴趣的:(数据结构,并查集,网络流)