這次練習主要是復習回溯法,之前一練主要還是學習了子集樹與排序樹的基本操作。
主要內容
回顧知識:數字全排列(子集樹、排序樹)
回溯法之加強版:素數環
練習題:數字排序問題(藍橋杯) + 39級台階 + 數字排列(相鄰之和為素數)
“溫故而知新,可以為師矣“
全排列問題
【問題描述】
給定數字n,請輸出1~n的全部排列順序。
例如,n=3,輸出:{1,2,3},{1,3,2},{2,1,3},{2,3,1},{3,1,2},{3,2,1}
【題解】
利用兩種回溯法的代表解決該問題。
子集樹做法:借助 vis[ ] 來標記其在集合中的元素。
構建搜索樹,其每一層相當於排列中的每一個位置。
用參數step控制當前位置
該位置填入什么數字,還需看vis[ ]是否被標記過
若該數字已被集合選中,不做處理,則找另外的數字
若該數字未被選中,則把該位置賦值上該數字。同時進行下一層搜索。
當到達葉子結點時,則進行回溯。
回溯時別忘記把當前位置的數字撤標記。
對於當前位置來說又可以填入另外的數字。
其核心代碼:
for( int i = 1 ; i <= n ; i ++ ){ if( vis[i] == 0 ){ vis[i] = 1 ; A[step] = i ; dfs_subset( step + 1 ); vis[i] = 0 ; } }
排列樹做法:
由於排列必須時n個數,別忘記要初始化數字,直接給賦上值。(1~n)
然后進行排列樹的常規操作。
構建搜索樹,其每一層相當於排列中的每一個位置。
用參數<S,E>控制,S表示當前位置,E表示結束位置。
我們所需要的是對下標為S,即當前位置的位置分別與后面的數字交換操作。
其目的就是為達到該位置 把全部數字在該位置輪流打頭。
到達葉子結點的條件是,當前位置S已經是結束位置E
核心代碼:
for( int i = S ; i <= E ; i++ ){ swap( B[S] , B[i] ); dfs_Permutation( S+1 , E ); swap( B[S] , B[i] ); }
具體代碼:

1 //DFS 利用子集樹 和 排列樹 實現數字全排列 2 #include<cstdio> 3 #include<algorithm> 4 using namespace std; 5 const int N = 20 ; 6 int vis[N],n; 7 8 int A[N] , B[N]; 9 void dfs_subset(int step){ 10 if( step == n ){ 11 for( int i = 0 ; i < n ; i++ ){ 12 printf("%3d",A[i]); 13 } 14 putchar('\n'); 15 } 16 for( int i = 1 ; i <= n ; i ++ ){ 17 if( vis[i] == 0 ){ 18 vis[i] = 1 ; A[step] = i ; 19 dfs_subset( step + 1 ); 20 vis[i] = 0 ; 21 } 22 } 23 } 24 25 void dfs_Permutation( int S , int E ){ 26 if( S == E ){ 27 for( int i = 1 ; i <= n ; i++ ){ 28 printf("%3d",B[i]); 29 } 30 putchar('\n'); 31 return ; 32 } 33 for( int i = S ; i <= E ; i++ ){ 34 swap( B[S] , B[i] ); 35 dfs_Permutation( S+1 , E ); 36 swap( B[S] , B[i] ); 37 } 38 } 39 int main() 40 { 41 puts("Please input N :"); 42 scanf("%d",&n); 43 44 puts(" dfs_subset tree"); 45 dfs_subset(0); 46 47 puts(" dfs_Permutation tree"); 48 for( int i = 1 ; i <= n ; i++ ){ 49 B[i] = i ; 50 } 51 dfs_Permutation(1,n); 52 return 0; 53 }
素數環
【題目描述】
素數環是一個計算機程序問題,指的是將從1到n這n個整數圍成一個圓環,若其中任意2個相鄰的數字相加,結果均為素數,那么這個環就成為素數環。現在要求輸入一個n,求n個數圍成一圈有多少種素數環,規定第一個數字是1。
【小結】
大家千萬不要去新的服務器交題目。我都快自閉了。然后去了以前的服務器上交題過了。但是那個題目的數據有錯,應該需要特判1,因為1本身不是素數。而那道題目的數據雖然涉及n=1的情況,但是答案卻是1,答案應該是“-1”。
【題解】
全排列的一個變種問題,其實就是在過程中加一個判斷即可。在放數字判斷前一個數與當前位置需要放的數之和是否為質數。因為是一個環,所以最后還需要加一個判斷,即最后一個數與1相加是否為質數。
總結:朴素全排列問題+過程中特判相鄰數之和為質數+最后一個數與1相加為質數。

1 //dfs解決素數環 hdu-1016 2 // http://acm.hdu.edu.cn/showproblem.php?pid=1016 3 #include<cstdio> 4 #include<cstring> 5 using namespace std; 6 const int N = 1e3 +10 ; 7 8 int n ; 9 int a[N] ; 10 int vis[N] ; 11 bool is_prime[N] ; 12 13 void Init(){ 14 for( int i = 2 ; i < N ; i++ ) { 15 bool f = true; 16 for (int j = 2; j * j <= i; j++) { 17 if (i % j == 0) { 18 f = false; 19 break; 20 } 21 } 22 if (f) is_prime[i] = true; 23 24 } 25 a[1] = 1 ; vis[1] = -1 ; 26 } 27 28 void dfs_subset( int step ){ 29 if( step == n + 1 ){ 30 if( is_prime[ a[n] + 1 ] ){ 31 for( int i = 1 ; i <= n ; i ++ ){ 32 printf("%d%c",a[i],i==n?'\n':' '); 33 //printf("%d ",a[i]); 34 } 35 //putchar('\n'); 36 } 37 return ; 38 } 39 40 for( int i = 2 ; i <= n ; i++ ) { 41 if (vis[i] == 0 && is_prime[i + a[step - 1]]) { 42 vis[i] = 1; a[step] = i; 43 dfs_subset(step + 1); 44 vis[i] = 0; 45 } 46 } 47 } 48 int main() 49 { 50 Init() ; 51 int Case = 0 ; 52 while( ~scanf("%d",&n) ){ 53 printf("Case %d:\n",++Case); 54 if( n > 20 ){ 55 printf("-1\n"); 56 }else if( n % 2 == 1 ){ 57 printf("-1\n"); 58 }else{ 59 dfs_subset( 2 ); 60 } 61 puts(""); 62 } 63 return 0 ; 64 }
練習題
數字排列問題
【題目描述】
今有7對數字:兩個1,兩個2,兩個3,...兩個7,把它們排成一行。 要求,兩個1間有1個其它數字,兩個2間有2個其它數字,以此類推,兩個7之間有7個其它數字。如下就是一個符合要求的排列: 17126425374635 當然,如果把它倒過來,也是符合要求的。 請你找出另一種符合要求的排列法,並且這個排列法是以74開頭的。 注意:只填寫這個14位的整數,不能填寫任何多余的內容,比如說明注釋等。 744*7***
【題解】
直接全排列公式用上,同時要多增加一個標記數組,每個位置放一個數的同時相隔i+1的位置同時放。
然后就OK了。

1 //74****4*7******* 2 #include<cstdio> 3 #include<cstring> 4 using namespace std; 5 const int N = 20 ; 6 int vis[N] , a[N]; 7 void dfs( int step ){ 8 if( step == 14 ){ 9 for( int i = 1 ; i <= 14 ; i++ ){ 10 printf("%d%c",a[i],i==14?'\n':' '); 11 } 12 return ; 13 } 14 15 if( a[step] != -1 ) dfs( step + 1 ); 16 17 for( int i = 1 ; i <= 6 ; i ++ ){ 18 if( a[step] == -1 && vis[i] == 0 && step + i + 1 <= 14 && a[ step + i + 1] == -1 ){ 19 vis[i] = 1 ; 20 a[step + i + 1] = a[step] = i ; 21 22 dfs( step + 1 ); 23 24 a[step + i + 1] = a[step] = -1 ; 25 vis[i] = 0 ; 26 } 27 } 28 } 29 int main(){ 30 for( int i = 1 ; i <= 14 ; i ++ ) a[i] = -1 ; 31 vis[4] = vis[7] = -1 ; 32 a[1] = 7 , a[9] = 7 ; 33 a[2] = 4 , a[7] = 4 ; 34 dfs( 3 ) ; 35 return 0 ; 36 }
39級台階
【題目描述】
小明看完電影《第39級台階》,離開電影院的時候,他數了數視覺的台階數,恰好是39級。 站在台階前,他突然又想起一個問題:如果我每一步只能邁上1個或2個台階,先邁左腳,然后左右交替,最后一步邁右腳,也就是說一共要邁偶數步。那么,上完39級台階,有多少種不同的上法呢? 請利用計算機的優勢,幫助小明尋找答案。
利用遞歸來實現,從第0層往上走,上1級或2級。
題目都給出來了,先邁左腳,最后邁出右腳,可以說明步數為偶數。

1 #include<cstdio> 2 using namespace std; 3 int n = 39 ; 4 int ans = 0 ; 5 void dfs( int step , int k ){ 6 //定義結束條件 7 if( step == 39 && k % 2 == 0 ){ 8 ans ++ ; 9 return ; 10 } 11 //結束條件需要多加一條 12 if( step >= 40 ) return ; 13 14 //邁出一步,台階增加1或2 15 dfs( step + 1 , k + 1 ); 16 dfs( step + 2 , k + 1 ); 17 } 18 //51167078 19 int main() 20 { 21 dfs( 0 , 0 ); 22 printf("%d\n",ans); 23 return 0; 24 }
數字排列問題
【題目描述】
將1到20排一列,要求每相鄰兩位數的和是質數,試求排列的種數。
【題解】
用排列樹解決,弱化版的素數環問題。
預處理所有素數出來,用bool數組標識出來。
然后放每一個數的時候加上兩數之和為素數即可。
只不過剪枝效果不太好,等個2~3分鍾才出答案。。

1 //dfs解決數字排列問題 2 3 #include<cstdio> 4 #include<cstring> 5 #include<algorithm> 6 using namespace std; 7 const int N = 1e3 +10 ; 8 9 int n ; 10 int a[N] ; 11 bool is_prime[N] ; 12 13 void Init(){ 14 for( int i = 2 ; i < N ; i++ ) { 15 bool f = true; 16 for (int j = 2; j * j <= i; j++) { 17 if (i % j == 0) { 18 f = false; 19 break; 20 } 21 } 22 if (f) is_prime[i] = true; 23 } 24 } 25 26 int ans = 0 ; 27 void dfs_Permutation( int S , int E ){ 28 if( S == E && is_prime[ a[E] + a[E-1] ]){ 29 ans ++ ; 30 return ; 31 } 32 for( int i = S ; i <= E ; i++ ) { 33 if( S == 1 || ( is_prime[ a[i] + a[S-1] ] ) ){ 34 swap( a[S] , a[i] ); 35 dfs_Permutation( S + 1 , E ); 36 swap( a[S] , a[i] ); 37 } 38 } 39 } 40 int main() 41 { 42 Init() ; 43 n = 20 ; 44 for( int i = 1 ; i <= n ; i ++ ){ 45 a[i] = i ; 46 } 47 dfs_Permutation( 1 , n ); 48 printf("%d\n",ans); 49 return 0 ; 50 }