POJ2513解題報告

來源:互聯網
上載者:User

POJ2513題目連結

 

以為是很簡單的字典樹……哭……

歐拉迴路以前貌似用過,還有一點印象,並查集是真的沒有用過,各種搜尋,附一些連結,並寫寫自己的總結~

 

歐拉迴路:

1 定義

  歐拉通路 (歐拉跡) ——通過圖中每條邊一次且僅一次,並且過每一頂點的通路。

  歐拉迴路 (歐拉閉跡) ——通過圖中每條邊一次且僅一次,並且過每一頂點的迴路。

  歐拉圖 ——存在歐拉迴路的圖。

2 無向圖是否具有歐拉通路或迴路的判定

  G有歐拉通路的充分必要條件為:G 連通,G中只有兩個奇度頂點(它們分別是歐拉通路的兩個端點)。

  G有歐拉迴路(G為歐拉圖):G連通,G中均為偶度頂點。

3 有向圖是否具有歐拉通路或迴路的判定

  D有歐拉通路:D連通,除兩個頂點外,其餘頂點的入度均等於出度,這兩個特殊的頂點中,一個頂點的入度比出度大1,另一個頂點的入度比出度小1。

  D有歐拉迴路(D為歐拉圖):D連通,D中所有頂點的入度等於出度。

 

在這個問題裡面,需要進行判定的就是沒有超過兩個以上的奇數個點,並且是聯通的。是否為奇數可以用一個暫存的數組儲存之,是否連通需要並查集的知識了,下面先貼連結,再說自己的想法:(寫到這裡真是後悔之前做POJ沒有寫過解題報告,那一百多道題真是做了就忘了……浪費啊……可恥啊……教訓啊……)


這個哥們的並查集總結比較詳細,對原理什麼的說的也比較清楚:連結

這個是大牛,裡面的東西很多,貼之Mark之羨慕嫉妒恨之

 

=============================從這裡開始摘自第一個連結====================================

l        並查集:(union-find
sets)

一種簡單的用途廣泛的集合. 並查集是若干個不相交集合,能夠實現較快的合并和判斷元素所在集合的操作,應用很多,如其求無向圖的連通分量個數等。最完美的應用當屬:實現Kruskar演算法求最小產生樹。

l        並查集的精髓(即它的三種操作,結合實現代碼模板進行理解):

1、Make_Set(x) 把每一個元素初始化為一個集合

初始化後每一個元素的父親節點是它本身,每一個元素的祖先節點也是它本身(也可以根據情況而變)。

2、Find_Set(x) 尋找一個元素所在的集合

尋找一個元素所在的集合,其精髓是找到這個元素所在集合的祖先!這個才是並查集判斷和合并的最終依據。
判斷兩個元素是否屬於同一集合,只要看他們所在集合的祖先是否相同即可。
合并兩個集合,也是使一個集合的祖先成為另一個集合的祖先,具體見

3、Union(x,y) 合并x,y所在的兩個集合

合并兩個不相交集合操作很簡單:
利用Find_Set找到其中兩個集合的祖先,將一個集合的祖先指向另一個集合的祖先。

 

l        並查集的最佳化

1、Find_Set(x)時 路徑壓縮
尋找祖先時我們一般採用遞迴尋找,但是當元素很多亦或是整棵樹變為一條鏈時,每次Find_Set(x)都是O(n)的複雜度,有沒有辦法減小這個複雜度呢?
答案是肯定的,這就是路徑壓縮,即當我們經過"遞推"找到祖先節點後,"回溯"的時候順便將它的子孫節點都直接指向祖先,這樣以後再次Find_Set(x)時複雜度就變成O(1)了,如所示;可見,路徑壓縮方便了以後的尋找。

2、Union(x,y)時 按秩合并
即合并的時候將元素少的集合合并到元素多的集合中,這樣合并之後樹的高度會相對較小。

 

1int father[MAX]; /**//* father[x]表示x的父節點*/<br /> 2int rank[MAX]; /**//* rank[x]表示x的秩*/<br /> 3<br /> 4<br /> 5/**//* 初始化集合*/<br /> 6void Make_Set(int x)<br /> 7{<br /> 8 father[x] = x; //根據實際情況指定的父節點可變化<br /> 9 rank[x] = 0; //根據實際情況初始化秩也有所變化<br />10}<br />11<br />12<br />13/**//* 尋找x元素所在的集合,回溯時壓縮路徑*/<br />14int Find_Set(int x)<br />15{<br />16 if (x != father[x])<br />17 {<br />18 father[x] = Find_Set(father[x]); //這個回溯時的壓縮路徑是精華<br />19 }<br />20 return father[x];<br />21}<br />22<br />23<br />24/**//*<br />25 按秩合并x,y所在的集合<br />26 下面的那個if else結構不是絕對的,具體根據情況變化<br />27 但是,宗旨是不變的即,按秩合并,即時更新秩。<br />28*/<br />29void Union(int x, int y)<br />30{<br />31 x = Find_Set(x);<br />32 y = Find_Set(y);<br />33 if (x == y) return;<br />34 if (rank[x] > rank[y])<br />35 {<br />36 father[y] = x;<br />37 }<br />38 else<br />39 {<br />40 if (rank[x] == rank[y])<br />41 {<br />42 rank[y]++;<br />43 }<br />44 father[x] = y;<br />45 }<br />46}

=================================摘抄完畢===============================


簡單的來說,並查集也是一種做記錄的方式對集合進行合并:

1. 使能串連到同一元素的元素們指向相同的根,以此來代表他們屬於同一個集合。

2. 合并的時候根據rank的大小來合并,把小樹合并到大樹中去,這樣能夠有效減小樹的高度。

3. 文中強調的一點,"回溯"的時候順便將它的子孫節點都直接指向祖先。

 

最後字典樹就沒有什麼好說的了,AC的代碼:

#include <stdio.h><br />#include <string.h><br />const int MAXSIZE = 500002;<br />char s1[11],s2[11];<br />short p[MAXSIZE];<br />short r[MAXSIZE];<br />short degree[250001];<br />int num;<br />struct TreeNode<br />{<br />int id;//the id of the string<br />struct TreeNode * next[26];<br />TreeNode ():id(0)<br />{<br />memset(next,NULL,26*sizeof(TreeNode*));<br />}<br />};<br />TreeNode * root;<br />int Insert(char *s)<br />{<br />TreeNode *p = root;<br />int i = 0;<br />int l = strlen(s);<br />for(i=0;i<l;i++)<br />{<br />if(p->next[s[i]-'a']==NULL)<br />p->next[s[i]-'a']=new TreeNode;<br />p=p->next[s[i]-'a'];<br />}<br />if(p->id==0)//first insert<br />p->id = ++num;<br />return p->id;<br />}<br />void MakeSet(int x)<br />{<br />p[x]=x;<br />r[x]=1;<br />}<br />int FindSet(int x)<br />{//這裡不把遞迴修正為while迴圈的目的是隨著遞迴修正父節點!!!<br />if(p[x]!=x)<br />p[x]=FindSet(p[x]);<br />return p[x];<br />}<br />void UnionSet(int x,int y)<br />{<br />if(x == y)<br />return;<br />if(r[x]>r[y])<br />p[y]=x;<br />else<br />{<br />if(r[x] == r[y])<br />++r[y];<br />p[x]=y;<br />}<br />}<br />int main()<br />{<br />root = new TreeNode;<br />for (int i = 0;i<MAXSIZE;++i)<br />p[i] = i;<br />int id1,id2,sum=0;<br />while(scanf("%s %s",s1,s2)!=EOF)<br />{<br />id1 = Insert(s1);<br />id2 = Insert(s2);<br />++degree[id1];<br />++degree[id2];<br />UnionSet(FindSet(id1),FindSet(id2));<br />}<br />for(int i=1;i<num;++i)<br />{<br />if(degree[i] & 0x00000001)<br />++sum;<br />if(sum>2)<br />{<br />printf("Impossible/n");<br />return 0;<br />}<br />}<br />for(int i=1;i<num;++i)<br />if(FindSet(i) != FindSet(1))<br />{<br />printf("Impossible/n");<br />return 0 ;<br />}<br />printf("Possible/n");<br />return 0;<br />}

 

以此為基礎,順便A掉了2524和1611(都是並查集的題目,比較簡單)。

現在在嘗試寫FindSet的非遞迴版本,代碼如下~

非遞迴版本的FindSet函數實現:

int FindSet(int x)<br />{<br />int y,z,root;<br />y = p[x];<br />p[x] = x;<br />while (x != y)<br />{//first loop is to find the root<br />z= p[y];<br />p[y] = x;//reversing parent's pointer!<br />y = z;// point to their father, respectively<br />x = y;<br />}<br />root = x;//x==y now<br />y = p[x];//the pointer has been reversed!<br />p[x] = root;<br />while (x != y)<br />{//the second loop is re-new the pointer<br />z= p[y];<br />p[y] = root;//reversing parent's pointer!<br />x = y;<br />y = z;// point to their father, respectively<br />}<br />return root;<br />}

這個函數的實現其實在本質上就是要理解路徑壓縮的過程,原來的遞迴版本其實是一種兩趟搜尋的方法,一趟沿尋找路徑上升,直到找到根;一趟沿尋找路徑下降,以便更新每一個節點,使之直接指向根(摘自演算法導論SE P.311)。

非遞迴的辦法也是使用while迴圈類比這種兩趟的過程,第一個迴圈讓根指向元素,第二個迴圈則把對應元素的根全部用最終的root來代替,最後返還root。

 

POJ1161-Code

//最小並查集<br />#include <stdio.h><br />#include <memory.h><br />int p[30010];<br />int r[30010];<br />void Initialize(int n)<br />{<br />for (int i = 0;i<=n;++i)<br />{<br />p[i] = i;<br />r[i] = 1;<br />}<br />//memset(r,0,sizeof(r));<br />}<br />int FindSet(int x)<br />{<br />// if(x!=p[x])<br />// p[x] = FindSet(p[x]);<br />// return p[x];<br />int y,z,root;<br />y = p[x];<br />p[x] = x;<br />while (x != y)<br />{//first loop is to find the root<br />z= p[y];<br />p[y] = x;//reversing parent's pointer!<br />y = z;// point to their father, respectively<br />x = y;<br />}<br />root = x;//x==y now<br />y = p[x];//the pointer has been reversed!<br />p[x] = root;<br />while (x != y)<br />{//the second loop is re-new the pointer<br />z= p[y];<br />p[y] = root;//reversing parent's pointer!<br />x = y;<br />y = z;// point to their father, respectively<br />}<br />return root;<br />}<br />void UnionSet(int x, int y)<br />{<br />// if(x == y)<br />// return;<br />// if(r[x]<=r[y])<br />// {<br />// p[x] = y;<br />// r[y]+=r[x];<br />// }<br />// else<br />// {<br />// p[y] = x;<br />// r[x]+=r[y];<br />// }<br />if(x == y)<br />return;<br />if(r[x]<r[y])<br />{<br />p[x] = y;<br />}<br />else<br />{<br />p[y] = x;<br />}<br /> if(r[x] == r[y])<br /> ++r[y];<br />}<br />int main()<br />{<br />int n ,m;<br /> while (scanf("%d%d",&n,&m),m|n)<br /> {<br /> Initialize(n);<br />int cnt = 0;<br /> for (int i = 0;i<m;++i)<br /> {<br /> int k,id1,id2;<br /> scanf("%d%d",&k,&id1);<br />while (--k)<br />{<br />scanf("%d",&id2);<br />UnionSet(FindSet(id1),FindSet(id2));<br />}<br /> }<br /> for (int i = 0;i<n;++i)<br /> {<br /> if(FindSet(0)==FindSet(i))<br /> ++cnt;<br /> }<br />printf("%d/n",cnt);<br />//printf("%d[%d]/n",FindSet(0),r[FindSet(0)]);<br /> }<br />return 0;<br />}

 

POJ2524-Code

#include <stdio.h><br />const int MAXSIZE = 50001;<br />int p[MAXSIZE];<br />int rank[MAXSIZE];<br />void Initialize(int n)<br />{<br />for (int i = 1;i<=n;++i)<br />{<br />p[i] = i;<br />rank[i] = 1;<br />}<br />p[0] = n;<br />//memset(r,0,sizeof(r));<br />}<br />int FindSet(int x)<br />{<br />if(x!=p[x])<br />p[x] = FindSet(p[x]);<br />return p[x];<br />}<br />void UnionSet(int x, int y)<br />{<br />if(x == y)<br />return;<br />if(rank[x]<rank[y])<br />{<br />p[x] = y;<br />}<br />else<br />{<br />p[y] = x;<br />}<br />if(rank[x] == rank[y])<br />++rank[y];<br />--p[0];<br />}<br />int main()<br />{<br />int n,m,num= 0;<br />while (scanf("%d%d",&n,&m),m|n)<br />{<br />++num;<br />int x,y;<br />Initialize(n);<br />while (m--)<br />{<br />scanf("%d%d",&x,&y);<br />UnionSet(FindSet(x),FindSet(y));<br />}<br />printf("Case %d: %d/n",num,p[0]);<br />}<br />return 0;<br />}

對於2524一個簡單說說的地方就是把P[0]作為記錄子樹數目的節點,初始化為n,當執行Union操作的時候,如果x!=y則執行--p[0]。

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.