組合補考的我且先裝個比,競賽圖中求漢密爾頓圈演算法設計
對於一個圖中是否存在一條哈密頓路,沒有可靠的充分必要條件(貌似鄰接矩陣恆式可以?),因此求哈密頓路是一個NP問題,一般要使用搜索和狀壓dp求解,但漢密爾頓迴路的存在有許多充分條件,即當圖滿足某些特定性質的時候,漢密爾頓迴路一定存在,而且可以根據一些演算法構造出來。
1.Dirac定理:設一個無向圖中有 N 個節點,若所有節點的度數都大於等於 N/2,則漢密爾頓迴路一定存在。
(“N/2” 中的除法不是整除,而是實數除法,該條件中的 “N/2” 等價於 “⌈N/2⌉”)
證明:首先可以證明圖一定是連通的。設 d(v) 表示節點 v 的度數。對於任意兩個節點 u、 v,若它們不相鄰,則可能和它們相鄰的節點共有 N - 2 個,而 d(u) + d(v) ≥
構造方法:
1. 任意找兩個相鄰的節點 S 和 T,在它們基礎上擴展出一條儘量長的沒有重複節點的路徑。也就是說,如果 S 與節點 v 相鄰,而且 v 不在路徑 S → T 上,則可以把該路徑變成 v → S → T,然後 v 成為新的 S。從 S 和 T 分別向兩頭擴充套件,直到無法擴為止,即所有與 S 或 T 相鄰的節點都在路徑 S → T 上。
2. 若 S 與 T 相鄰,則路徑 S → T 形成了一個迴路。
3. 若 S 與 T 不相鄰,可以構造出一個迴路。設路徑 S →
4. 現在我們有了一個沒有重複節點的迴路。如果它的長度為 N,則漢密爾頓迴路就找到了。如果迴路的長度小於 N,由於整個圖是連通的,所以在該回路上,一定存在一點與迴路以外的點相鄰。那麼從該點處把迴路斷開,就變回了一條路徑。再按照步驟1的方法儘量擴充套件路徑,則一定有新的節點被加進來。接著回到步驟 2。
在整個構造過程中,如果說每次到步驟 4 算是一輪的話,那麼由於每一輪當中,至少有一個節點被加入到路徑 S → T 中來,所以總的輪數肯定不超過 N 輪。實際上,不難看出該演算法的複雜度就是 O(N^2),因為總共擴充套件了 N 步路徑,每步擴充套件最多列舉所有的節點。
2.競賽圖:n(n>=2)階競賽圖一定存在哈密頓通路
證明:對n作歸納法。n=2時,D的基圖為K2,結論成立。設n=k時結論成立。現在設n=k+1.設V(D)={v1,v2,…,vk,vk+1}。令D1=D-vk+1,易知D1為k階競賽圖,由歸納假設可知,D1存在哈密頓通路,設Г1=v'1v'2…v'k為其中一條。下面證明vk+1可擴到Г1中去。若存在v'r(1≤r≤k),有∈E(D),i=1,2,…,r-1,而∈E(D),見圖(1)所示,則Г=v'1v'2…v'r-1vk+1v'r…v'k為D中哈密頓通路。否則,i∈{1,2,…,k},均有∈E(D),見下圖所示,則Г=Г'∪為D中哈密頓通路。
==================================================================================================
總的來說,無論是有向圖還是無向圖,求漢密爾頓路都適合用前面介紹的“擴充套件”的辦法。
而漢密爾頓迴路就比較特殊了。
對於競賽圖(每兩個點之間有且只有一條有向邊),一定有漢密爾頓路,當且僅當競賽圖是強連通的,存在漢密爾頓迴路,這個可以在“擴充套件”的過程中判斷,也可以用Tarjan或Kosaraju演算法提前判斷。
[cpp]view plaincopyprint?
1. #include<iostream>
2. #include<cstdio>
3. #include<string>
4. #include<cstring>
5. #include<vector>
6. #include<cmath>
7. #include<queue>
8. #include<stack>
9. #include<map>
10. #include<set>
11. #include<algorithm>
12. using namespace std;
13. const int maxn=1100;
14. int N;
15. int g[maxn][maxn];
16. int nex[maxn];
17. bool expend(int st)
18. {
19. for(int i=0;i<N;i++)nex[i]=-1;
20. int head=st,tail=head;
21. for(int i=0;i<N;i++)
22. {
23. if(i==st)continue;
24. if(g[i][head])nex[i]=head,head=i;
25. else
26. {
27. int x=head,y=nex[head];
28. while(y!=-1&&g[y][i])
29. {
30. x=y;
31. y=nex[y];
32. }
33. nex[x]=i;
34. nex[i]=y;
35. if(y==-1)tail=i;
36. }
37. }
38. if(g[tail][head])
39. {
40. nex[tail]=head;
41. return true;
42. }
43. return false;
44. }
45. bool solve()
46. {
47. for(int i=0;i<N;i++)
48. if(expend(i))return true;
49. return false;
50. }
51. int main()
52. {
53. while(scanf("%d",&N)!=EOF,N)
54. {
55. for(int i=0;i<N;i++)
56. for(int j=0;j<N;j++)
57. scanf("%d",&g[i][j]);
58. if(N==1){printf("1\n");continue;}
59. if(N==2||!solve())printf("-1\n");
60. else
61. for(int i=0,j=0;i<N;i++,j=nex[j])
62. printf("%d%c",j+1,i==N-1?'\n':' ');
63. }
64. return 0;
65. }
Problem Description
The city is so crowded that the mayor can't bear any longer. He issued an order to change all the roads into one-way street. The news is terrible for Jack, who is the director of a tourism company, because he has to change the travel route. All tourists want to set out from one scenic spot, then go to every scenic spots once and only once and finally return to the starting spot. They don’t care about which spot to start from, but they won’t go back to the starting spot before they have visited all other spots. Fortunately, the roads in the city have been perfectly built and any two scenic spots have been connected by ONE road directly. Jack gives the map of the city to you, and your task is to arrange a new travel route around the city which can satisfy the tourists.
Input
Input consists of multiple test cases and ends with a line of “0”.
For each test case:
The first line contains a single integer n (0
Then n lines follows, and each line consists of n integers. These n lines make a matrix. If the element in the ith row and the jth column is 1(i≠j), it means that the direction of the road between spot i and spot j is from spot i to spot j. If that element is 0, it means that the road’s direction is from spot j to spot i. The numbers in the main diagonal of the matrix are all 0. (i and j start from 1)
Output
For each test case, print all the spots No. according to the traveling order of the route in one line. If multiple routes exist, just print one of them. If no such route exists, print a “-1” instead. Because the starting spot is the same as the ending spot, so you don’t need to print the ending spot.
This problem needs special judge.
Sample Input
5
0 0 1 1 1
1 0 1 1 0
0 0 0 1 0
0 0 0 0 1
0 1 1 0 0
2
0 1
0 0
0
Sample Output
1 3 4 5 2
-1
======================================================================================
這道題到現在我心裡還是虛的,poj3780明明是同一道題,相同的程式碼卻wa了,讓我懷疑自己是佔特判資料的便宜水過的。
這道題首先想到的方法是暴力深搜,不出所料的tle了。
後來做了優化,競賽圖當且僅當強連通時是漢密爾頓圖,於是我用Tarjan先把不是漢密爾頓圖的去掉。
Tarjan是線性效率所以我並不擔心。
然後我把入度為1的點跟它唯一的前驅縮成一個點,把出度為1的點跟它的唯一後繼縮成一個點,還是深搜。
依然超時,看來深搜是怎麼都不行了。
後來我看到了這個:http://blog.sina.com.cn/s/blog_8d84b9240101fdwj.html
於是有了下面的程式碼。
======================================================================================
#include<cstdio>
const int N = 1000;
bool g[N][N];
int n , next[N];
bool expand( int s )
{
for( int i = 0 ; i < n ; next[i++] = -1 );
int front = s , back = front;
for( int i = 0 ; i < n ; i++ )
{
if( i == s ) continue;
if( g[i][front] ) next[i] = front , front = i;
else
{
int a = front , b = next[front];
while( b != -1 && g[b][i] )
a = b , b = next[b];
next[a] = i;
next[i] = b;
if( b == -1 ) back = i;
}
}
if( g[back][front] )
{
next[back] = front;
return true;
}
return false;
}
bool solve()
{
for( int i = 0 ; i < n ; i++ )
if( expand(i) ) return true;
return false;
}
int main()
{
while( scanf("%d",&n) , n )
{
for( int i = 0 ; i < n ; i++ )
for( int j = 0 ; j < n ; j++ )
scanf("%d",&g[i][j]);
if( n == 1 ) puts("1");
else if( n == 2 || !solve() ) puts("-1");
else
for( int i = 0 , j = 0 ; i < n ; i++ , j = next[j] )
printf("%d%c",j+1,i==n-1?'\n':' ');
}
return 0;
}