回溯:把问题分解成若干个步骤并且递归求解时,如果当前步骤没有合法的选择,则返回上一级递归调用。
优点:回溯法在递归中边生成边检查,从而减少了大量不必要的枚举量。
- N皇后问题 HDU 2553
注意:先打个表以防超时
分析:以八皇后为例
1.最简单的思路是把问题转化为从64个格子中选出一个子集,使子集中恰好有8个格子,且这8个格子满足八皇后的要求。这样做子集个数为2^64,太大。
2.第二种方法是在64个格子中选8个格子,组合问题,C(8,64)=4.426×10^9,仍旧很大。
3.第三种方法,恰好每行每列放置一个皇后最多可能性为8!=40320,比前两种方法小太多了。
代码注释:
1.c[i]=j 代表 (i,j) 点。
2.小兔子处是回溯法减少枚举量的根源所在。
3.cur-c[cur] 和 cur+c[cur] 处理主副对角线放置冲突。
4.vis[0][]、vis[1][]、vis[2][]代表了3种状态,分别是列是否冲突,主对角线是否冲突,副对角线是否冲突。
写法1:
#include<cstdio> using namespace std; int tot; int c[101]; void serch(int cur,int n){ if(cur==n){ tot++; return; } for(int i=0;i<n;i++){ bool ok=true; c[cur]=i; for(int j=0;j<cur;j++){ if(c[cur]==c[j] || cur-c[cur]==j-c[j] || cur+c[cur]==j+c[j]){ // (T m T) Here! ok=false;break; } } if(ok) serch(cur+1,n); } } int main(){ int n,ans[20]; for(int i=1;i<=10;i++){ tot=0; serch(0,i); ans[i]=tot; } while(scanf("%d",&n)!=EOF && n){ printf("%d\n",ans[n]); } return 0; }
写法2:
注意:注意在回溯的时候需要消除标记,相对于写法1来说,写法2效率更高,因为它并不需要再遍历前cur-1行放置的位置了。
#include<cstdio> using namespace std; int tot; int c[20],vis[3][20]; void serch(int cur,int n){ if(cur==n) tot++; else for(int i=0;i<n;i++){ if(!vis[0][i] && !vis[1][i+cur] && !vis[2][cur-i+n]){ vis[0][i]=vis[1][i+cur]=vis[2][cur-i+n]=1; serch(cur+1,n); vis[0][i]=vis[1][i+cur]=vis[2][cur-i+n]=0; } } } int main(){ int n,ans[20]; for(int i=1;i<=10;i++){ tot=0; serch(0,i); ans[i]=tot; } while(scanf("%d",&n)!=EOF && n){ printf("%d\n",ans[n]); } return 0; }
写法3:
这是之前写的代码,不如代码2简洁美观
#include<iostream> #include<cstdio> using namespace std; const int Max = 30; //这里为什么是开到30,因为最大是10*10所以至少应该开到21才对(C[x-y+n],D[x+y]),吼吼~ bool vis[Max][Max],A[Max],B[Max],C[Max],D[Max]; //A,B,C,D分别为上,下,左上到右下,右上到左下 int ans[Max]={0}; int n,cnt; bool OK(int x,int y){ if(vis[x][y] || A[x] || B[y] || C[x-y+n] || D[y+x]) return false; return true; } void Set(int x,int y){ vis[x][y] = A[x] = B[y] = C[x-y+n] = D[y+x] = true ; } void Delete(int x,int y){ vis[x][y] = A[x] = B[y] = C[x-y+n] = D[y+x] = false ; } void DFS(int x){ //传入一个行 for(int y=1;y<=n;y++){ //搜索列 if( OK(x,y) ){ if(x==n){ cnt++; break; } Set(x,y); DFS(x+1); Delete(x,y); } } } int main(){ for(n=1;n<=10;n++){ cnt=0; DFS(1); ans[n]=cnt; // printf("%d皇后=%d\n",n,ans[n]); } while(scanf("%d",&n)!=EOF && n){ printf("%d\n",ans[n]); } return 0; }
- 素数环 Prime Ring HDU 1016
分析: 1.最简单的思路是直接枚举,排列总数高达n! (n<=16) ,16!=2*10^13
2.第二种思路是回溯法,按照深度优先遍历的顺序遍历解答树。
注意:如果在最坏的情况下枚举量很大,应该尝试回溯法。
#include<cstring> #include<cstdio> using namespace std; const int MAX = 30; // 0<n<20 int A[MAX],vis[MAX]; int isp[MAX]; int n,k=0; void Prime(){ for(int i=0;i<MAX;i++) isp[i]=1; isp[0]=isp[1]=0; for(int i=2;i*i<MAX;i++){ if(isp[i]) for(int j=2*i;j<MAX;j+=i) isp[j]=0; } } void serch(int cur,int n){ if(cur==n && isp[A[0]+A[n-1]]){ for(int i=0;i<n;i++) printf("%d%c",A[i],i==n-1?'\n':' '); return; } else for(int i=2;i<=n;i++){ if(!vis[i] && isp[i+A[cur-1]]){ //如果符合要求 A[cur]=i; vis[i]=1; serch(cur+1,n); vis[i]=0; } } } int main(){ Prime(); // for(int i=1;i<20;i++) printf("%d=%d ",i,isp[i]); while(scanf("%d",&n)!=EOF){ printf("Case %d:\n",++k); memset(vis,0,sizeof(vis)); A[0]=1; serch(1,n); if(k>=1) printf("\n"); } return 0; }
- 超级氪因素大赛 UVa 129 Krypton Factor
原题链接:Here!
题目大意:Here!
分析:从左向右依次考虑每个位置上的字符。然后判断当前字符串中是否存在连续的重复子串。关键是如何判断?最直接的办法是找到当前字符串所有长度为偶数的子串,然后判断子串左半边和右半边是否相同。当然这样做了很多无用功。当前串是上一级递归的字符串+一个后缀字符,因为上一级递归字符串是合法的,只需要判断加入后缀后构成的字符串是否符合要求即可,而非所有子串。
注意:一定要注意输出格式,因为没读懂这个输出个是,PE无数次。
思考:应该避免不需要的判断,精髓。
#include<cstdio> using namespace std; int S[100]; int n,L,cnt; int serch(int cur){ //传入字符串长度 /*查看前n个hard串 for(int i=0;i<cur;i++){ if(i) if(i%4==0) printf(" "); printf("%c",'A'+S[i]); } printf("\n");*/ if(cnt++ == n){ for(int i=0;i<cur;i++){ if(i){ if(i%64){ if(i%4==0) printf(" "); } else printf("\n"); } printf("%c",'A'+S[i]); } printf("\n%d\n",cur); return 0; } for(int i=0;i<L;i++){ //对每个字母都进行尝试 S[cur]=i; int ok=1; for(int j=1;j*2<=cur+1;j++){ //尝试长度为j*2的后缀 int equal=1; for(int k=0;k<j;k++) if(S[cur-k]!=S[cur-k-j]){ equal=0; break; } if(equal){ ok=0; break; } //如果后一半等于前一半,不符合要求 } if(ok && !serch(cur+1)) return 0; //递归搜索,如果已经找到解,直接退出 } return 1; } int main(){ while(scanf("%d%d",&n,&L)!=EOF && n+L){ cnt=0; serch(0); } return 0; }