1. 关于欧拉路(Euler’s Path)和欧拉回路(Euler’s Circle), 含有欧拉回路的图又叫欧拉图
如果给定无孤立结点图G,若存在一条路,经过图中每边一次且仅一次,这条路称为欧拉路;
如果给定无孤立结点图G,若存在一条回路,经过图中每边一次且仅一次,那么该回路称为欧拉回路。
存在欧拉回路的图,称为欧拉图。
一、 对于无向图G,具有一条欧拉路,当且仅当G是连通的,且有零个或两个奇数度结点。
且有零个奇数度结点,存在欧拉回路;有两个奇数度结点,存在欧拉路。
判断无向图G是否连通,可以从任意结点出发,进行深度优先遍历,如果可以遍历到所有点,也可以用并查集,判断根节点的个数,
说明,图G连通,否则,图G不连通。
二 、对于有向图G,具有一条单向欧拉路,当且仅当G是连通的,且每个结点入度等于出度,或者,
除两个结点外,每个结点的入度等于出度,而这两个结点满足,一个结点的入度比出度大1,
另一个结点的入度比出度小1。
判断有向图G是否连通,可以某一结点出发,进行深度优先遍历,如果存在一点作为初始结点
可以遍历到所有点,说明,图G连通,否则,图G不连通。
2. ⒈凡是由偶点组成的连通图,一定可以一笔画成。画时可以把任一偶点为起点,最后一定能以这个点为终点画完此图。
⒉凡是只有两个奇点的连通图(其余都为偶点),一定可以一笔画成。画时必须把一个奇点为起点,另一个奇点终点。
⒊其他情况的图都不能一笔画出。(奇点数除以二便可算出此图需几笔画成。)
3. 要明白为什么需要并查集。因为在一开始输入数据的时候使用树的话,首先如果数据太多,需要的存储内存就很庞大了。比如一棵树包括一个本节点的value和两个指针。这样的话需要的内存大概是10B,如果2Billions的节点,需要大概2……24B,大概1.5GB,这是无法承受的。(貌似用了并查集依旧是树,这个问题先提出),然后是查找速度的问题。如果使用hash,因为一开始输入的时候你不知道这个节点属于哪个块(输入的时候本节点和别的节点的关系都是和相邻的点的关系,所以还需要查找某个定点以确定到底属于哪个块,比如原来的思路,用Map<String,ArrayList>来做,你还需要判断你要查的元素的邻边是不是在ArrayList里面。一样需要查找)。速度是o(n),这样很不好,所以需要优化,需要并查集,这样就需要o(1),查n个数才o(n).
4. 百度百科:并查集:
在一些有N个元素的集合应用问题中,我们通常是在开始时让每个元素构成一个单元素的集合,然后按一定顺序将属于同一组的元素所在的集合合并,其间要反复查找一个元素在哪个集合中。这一类问题近几年来反复出现在信息学的国际国内赛题中,其特点是看似并不复杂,但数据量极大,若用正常的数据结构来描述的话,往往在空间上过大,计算机无法承受;即使在空间上勉强通过,运行的时间复杂度也极高,根本就不可能在比赛规定的运行时间(1~3秒)内计算出试题需要的结果,只能用并查集来描述。
想说的一点就是用并查集应该在空间上无法优化
5. 并查集的主要操作:
初始化
把每个点所在集合初始化为其自身。
通常来说,这个步骤在每次使用该数据结构时只需要执行一次,无论何种实现方式,时间复杂度均为O(N)。
查找
查找元素所在的集合,即根节点。
合并
将两个元素所在的集合合并为一个集合。
通常来说,合并之前,应先判断两个元素是否属于同一集合,这可用上面的“查找”操作实现。
6. 包括对所有单个的数据建立一个单独的集合(即根据题目的意思自己建立的最多可能有的集合,为下面的合并查找操作提供操作对象)
在每一个单个的集合里面,有三个东西。
1,集合所代表的数据。(这个初始值根据需要自己定义,不固定)
2,这个集合的层次通常用rank表示(一般来说,初始化的工作之一就是将每一个集合里的rank置为0)。
3,这个集合的类别parent(有的人也喜欢用set表示)(其实就是一个指针,用来指示这个集合属于那一类,合并过后的集合,他们的parent指向的最终值一定是相同的。)
(有的简单题里面集合的数据就是这个集合的标号,也就是说只包含2和3,1省略了)。
初始化的时候,一个集合的parent都是这个集合自己的标号。
没有跟它同类的集合,那么这个集合的源头只能是自己了。
(最简单的集合就只含有这三个东西了,当然,复杂的集合就是把指针这一项添加内容,如PKU食物链那题,我们还可以添加enemy指针,表示这个物种集合的天敌集合;food指针,表示这个物种集合的食物集合。随着指针的增加,并查集操作起来也变得复杂,题目也就显得更难了)
7. 上面的实现可以用结构体和三个大小一致的数组。
至于TriedTree,则需要知道它的基本结构,就是一个class或者struct,包括三点:flag用来标记从根节点到此节点是否是一个完整的单词。Next[27]指针,用来知道下一个节点。这些index就是英文字母-‘a’,不会有char这个字段。另外,一般会有另外一个int字段,用来记录一些频率。
关于TriedTree的空间换时间: Trie树的根结点不包含任何信息,第一个字符串为"abc",第一个字母为'a',因此根结点中数组next下标为'a'-97的值不为NULL,其他同理,构建的Trie树如图所示,红色结点表示在该处可以构成一个单词。很显然,如果要查找单词"abc"是否存在,查找长度则为O(len),len为要查找的字符串的长度。而若采用一般的逐个匹配查找,则查找长度为O(len*n),n为字符串的个数。显然基于Trie树的查找效率要高很多。
但是却是以空间为代价的,比如图中每个结点所占的空间都为(26*4+1)Byte=105Byte,那么这棵Trie树所占的空间则为105*8Byte=840Byte,而普通的逐个查找所占空间只需(3+2+2+3)Byte=10Byte。
另外另一种说法也是很对的,就是如果数量很庞大的话,也许在空间上也不必普通的需要更大。
8. 至于本题,原来觉得可以用hash表来做。每读入一个就和已有的进行连接,这样看看到最后的结果。这是不知道并查集的思路。要明白并查集解决了什么问题。首先欧拉解决了证明问题,就是只需要找到节点数就可以。放到本题就是只需要知道每种颜色是不是奇数偶数就可以了。并查集解决的是查找,就是判定是否联通。原来觉得这可以从颜色的数目上面看出来,现在想想,看不出来。两个独立的欧拉回路颜色数目上看和一个欧拉回路一样。如果自己建个树,肯定不行,太慢了。只能用并查集。并查集构造的树和TriedTree可不一样。每个节点都是一种颜色,这和实际也很符合。只需要判断新来的颜色是不是位于已有的集合里面就可以了。所以,此处的树并没有像真正的树那样的形式,更多的是一种与数组结合的形式,不需要遍历,因为你只需要记得每个点的父亲是谁就可以了。也不用担心会找不到那些散列,因为有数组在。这大概和上面说的并查集的通常形式不同。TriedTree在本题中查的就是某种颜色是否出现过。如果用hash,是不能用String做key的。因为那样底层还是比较,只要用数字才能直接计算出地址。因为并查集里面需要index,也就是颜色的id。由此可知,本题的颜色非常多
9. 关于并查集的通常形式,以后再说。
10. 主要参考:
http://blog.csdn.net/lyy289065406/article/details/6647445
具体本题思路可以参考,并表示感谢。
#include<iostream>
#include<stdio.h>
#include<string.h>
using namespace std;
const int MAXN = 500000;
int color = 0;
class TriedTree
{
public:
int id;
bool flag;
//多了一个null
TriedTree* next[27];
//public需要加:
TriedTree()
{
id = 0;
flag = false;
memset(next,0,sizeof(next));
}
};
//TriedTree *root;
TriedTree root ;
int degree[MAXN+1]={0};
int ancestor[MAXN+1];
int find(int id)
{
//cout<<"in find: id: "<<id<<",ances:"<<ancestor[id]<<endl;
//cout<<(ancestor[id]!=id)<<endl;
//int a = (ancestor[id]!=id);
//cout <<a<<endl;
if((ancestor[id]!=id))
{
//cout <<"测试hiel)"<<endl;
ancestor[id] = find(ancestor[id]);
}
//cout <<"测试7"<<endl;
return ancestor[id];
}
void unionSet(int x, int y)
{
//cout<<"in union"<<endl;
int x2 = find(x);
int y2 = find(y);
ancestor[x2] = y2;
}
int hash(char *s)
{
TriedTree *p = &root;
int len = 0;
while(s[len]!='\0')
{
// cout<<"测试"<<s[len]<<endl;
int next = s[len++]-'a';
//cout<<"测试1.1:"<<p->next[next]<<endl;
//.和->的区别
if(p->next[next]==0)
{
//cout<<"测试1.2"<<s[len]<<endl;
p->next[next] = new TriedTree();
}
//cout<<"测试2"<<endl;
p = p->next[next];
}
if(p->flag)
{
return p->id;
}else {
p->flag = true;
p->id = ++color;
return p->id;
}
}
int main()
{
freopen("coloredStick.txt","r",stdin);
for(int i = 1; i <= MAXN;i++)
{
ancestor[i] = i;
}
char a[11],b[11];
while(cin>>a>>b)
{
int i = hash(a);
int j = hash(b);
//cout <<"i:"<<i<<",j:"<<j<<endl;
degree[i]++;
degree[j]++;
unionSet(i,j);
}
int s = find(1);
int num = 0;
for(int i = 1; i <= color; i++)
{
if(degree[i]%2!=0)
num++;
if(num>=3)
{
cout<<"Impossible"<<endl;
return 0;
}
if(find(i)!=s)
{
cout<<"Impossible"<<endl;
return 0;
}
}
if(num==1)
{
cout <<"Impossible"<<endl;
}else {
cout <<"Possible"<<endl;
}
return 0;
}