每日一题(3)poj2513: Colored Sticks

来源:互联网 发布:js监听视频播放状态 编辑:程序博客网 时间:2024/05/21 07:49

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;}


0 0
原创粉丝点击