POJ 2513 Colored Sticks (歐拉回路+並查集+字典樹)

題目連接node

Descriptionios

You are given a bunch of wooden sticks. Each endpoint of each stick is colored with some color. Is it possible to align the sticks in a straight line such that the colors of the endpoints that touch are of the same color?c++

Input算法

Input is a sequence of lines, each line contains two words, separated by spaces, giving the colors of the endpoints of one stick. A word is a sequence of lowercase letters no longer than 10 characters. There is no more than 250000 sticks.數組

Output數據結構

If the sticks can be aligned in the desired way, output a single line saying Possible, otherwise output Impossible.數據結構和算法

Sample Input優化

blue red
red violet
cyan blue
blue magenta
magenta cyanspa

Sample Output指針

Possible

分析:
大體題意:

給定一些木棒,木棒兩端都塗上顏色,求是否能將木棒首尾相接,連成一條直線,要求不一樣木棒相接的一邊必須是相同顏色的。

解題思路:

能夠用圖論中歐拉路的知識來解這道題,首先能夠把木棒兩端當作節點,把木棒當作邊,這樣相同的顏色就是同一個節點

問題便轉化爲:

給定一個圖,是否存在「一筆畫」通過塗中每一點,以及通過每一邊一次。

這樣就是求圖中是否存在歐拉路Euler-Path。

回顧經典的「七橋問題」,相信不少同窗立刻就明白了什麼是 歐拉路 了,這裏很少做解釋。

由圖論知識能夠知道,無向圖存在歐拉路的充要條件爲:

① 圖是連通的;

② 全部節點的度爲偶數,或者有且只有兩個度爲奇數的節點。

其中①圖的連通性用程序判斷比較麻煩,先放一下。

這裏先說說②關於度數的判斷方法:

blue red
red violet
cyan blue
blue magenta
magenta cyan
節點的度用顏色出現次數來統計,如樣例中,藍色blue出現三次(不論是出度仍是入度),那麼blue結點的度就爲3,一樣地,咱們也能夠經過輸入獲得其餘所有結點的度,因而,咱們有:
Blue=3

Red=2

Violet=1

Cyan=2

Magenta=2

用一個一維數組就能記錄了,而後分別 模2,就能判斷顏色結點的奇偶性

只要奇度數的結點數的個數 = 1 或 >=3 ,即便①圖連通,歐拉路也必不存在

可是若 奇度數的結點數的個數 爲0或 ==2,那麼咱們繼續進行①圖的連通性證實:

證實①圖的連通性,使用並查集MergeSet是很是高效的方法。

基本方法:

初始化所輸入的n個結點爲n棵樹,那麼就有一個n棵樹的森林,此時每棵樹的有惟一的結點(根),該結點的祖先就是它自己。再經過不斷地輸入邊,獲得某兩個結點(集合)之間的關係,進而合併這兩個結點(集合),那麼這兩個集合就構成一個新的集合,集合內的全部結點都有一個共同的新祖先,就是這個集合(樹)的根。

最後只要枚舉任意一個結點,他們都具備相同的祖先,那麼就能證實圖時連通的了。

可是單純使用並查集是會超時的,由於這樣會致使每次尋找某個結點的祖先時,平均都會花費O(n/2)時間,最壞狀況,當n==50W時,O(n/2)大概爲25ms,那麼要肯定50W個結點是否有共同祖先時,總費時爲50W*25ms ,鐵定超,不算了= =

所以必須使用並查集時必須壓縮路徑,前幾回搜索某個結點k的祖先時,在不斷經過父親結點尋找祖先結點時,順便把從k到最終祖先結點S中通過的全部結點的祖先都指向S,那麼之後的搜索就能把時間下降到O(1)

因爲並查集必須利用 數組的下標 與 存儲的對象,使用int是比較方便的處理方法,可是題目的「顏色結點」是string,不方便用來使用並查集,即便用map也不行,雖然STL的map是基於hash的基礎上,但並不高效,在本題中使用會超時。

爲此可使用Trie字典樹,獲得每一個顏色單詞對應的int編號id ,能夠說利用Trie把string一一映射到int,是本題後續處理的關鍵所在。關於動態建立字典樹的方法去百度,這裏很少說,下面只用用一個圖簡單說明一下用Trie字典樹標識第一個顏色單詞blue:

這個題目涉及了多個基本數據結構和算法,綜合性很強,很是有表明性,可以A到這題確實是受益良多。

知識考查點:

一、字典樹;

二、歐拉路:其中又考察了判斷是否爲連通圖;

三、並查集 及其優化方法(路徑壓縮)。

輸出:

POSSIBLE: 奇度數結點個數==0 或 ==2 且 圖連通

IMPOSSIBLE:奇度數結點個數==1 或 >=3 或 圖不連通

PS:注意建立TrieTree鏈表時,C++不存在NULL,要用 0 替代 NULL

代碼:

#include<iostream>
#include<stdio.h>
#include<algorithm>
#include<string.h>
using namespace std;

const int large=500000;  //25W條棒子,有50W個端點

class TrieTree_Node   //字典樹結點
{
public:
    bool flag;   //標記到字典樹從根到當前結點所構成的字符串是否爲一個(顏色)單詞
    int id;     //當前顏色(結點)的編號
    TrieTree_Node* next[27];

    TrieTree_Node()   //initial
    {
        flag=false;
        id=0;
        memset(next,0,sizeof(next));  //0 <-> NULL
    }
} root;  //字典樹根節點

int color=0;  //顏色編號指針,最終爲顏色總個數

int degree[large+1]= {0};  //第id個結點的總度數
int ancestor[large+1];   //第id個結點祖先

//尋找x結點的最終祖先

int find(int x)
{
    if(ancestor[x]!=x)
        ancestor[x]=find(ancestor[x]);   //路徑壓縮
    return ancestor[x];
}

//合併a、b兩個集合

void union_set(int a,int b)
{
    int pa=find(a);
    int pb=find(b);
    if(pa!=pb)
        ancestor[pb]=pa;   //使a的祖先 做爲 b的祖先
}

//利用字典樹構造字符串s到編號int的映射

int hash(char *s)
{
    TrieTree_Node * p=&root;  //從TrieTree的根節點出發搜索單詞(單詞不存在則建立)

    int len=0;
    while(s[len]!='\0')
    {
        int index=s[len++]-'a';  //把小寫字母a~z映射到數字的1~26,做爲字典樹的每一層的索引
        if(!p->next[index])  //當索引不存在時,構建索引
            p->next[index]=new TrieTree_Node;
        p=p->next[index];
    }

    if(p->flag)  //顏色單詞已存在
        return p->id;  //返回其編號
    else   //不然建立單詞
    {
        p->flag=true;
        p->id=++color;
        return p->id;   //返回分配給新顏色的編號
    }
}

int main(void)
{
    /*Initial the Merge-Set*/

    for(int k=1; k<=large; k++) //初始化,每一個結點做爲一個獨立集合
        ancestor[k]=k;  //對於只有一個結點x的集合,x的祖先就是它自己

    /*Input*/

    char a[11],b[11];
    while(cin>>a>>b)
    {
        /*Creat the TrieTree*/

        int i=hash(a);
        int j=hash(b);  //獲得a、b顏色的編號

        /*Get all nodes' degree*/

        degree[i]++;
        degree[j]++;   //記錄a、b顏色出現的次數(總度數)

        /*Creat the Merge-Set*/

        union_set(i,j);
    }

    /*Judge the Euler-Path*/

    int s=find(1);  //若圖爲連通圖,則s爲全部結點的祖先
    //若圖爲非連通圖,s爲全部祖先中的其中一個祖先

    int num=0;  //度數爲奇數的結點個數

    for(int i=1; i<=color; i++)
    {
        if(degree[i]%2==1)
            num++;

        if(num>2)   //度數爲奇數的結點數大於3,歐拉路必不存在
        {
            cout<<"Impossible"<<endl;
            return 0;
        }

        if(find(i)!=s)   //存在多個祖先,圖爲森林,不連通
        {
            cout<<"Impossible"<<endl;
            return 0;
        }
    }

    if(num==1) //度數爲奇數的結點數等於1,歐拉回路必不存在
        cout<<"Impossible"<<endl;
    else       //度數爲奇數的結點數剛好等於2或不存在,存在歐迴路
        cout<<"Possible"<<endl;

    return 0;
}
相關文章
相關標籤/搜索