題目連接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; }