回溯法是初学者学习暴力法的第一个障碍,所谓回溯就是指当把问题分成若干步骤并递归求解时,如果当前步骤没有合法选择,则函数将返回上一级递归调用,这种现象称为回溯。正是因为这个原因,递归枚举算法常被称为回溯法,应用十分普遍。
八皇后问题
1 int tot=0; 2 int c[maxn]; //c[x]表示x行所在的列数 3 void dfs(int n,int cur){ 4 if(cur==n) ++tot; 5 for(int i=0;i<n;i++){ 6 int flag=0; 7 c[cur]=i; 8 for(int j=0;j<cur;j++) 9 if(c[cur]==c[j]||(cur-c[cur]==j-c[j])||(cur+c[cur]==j+c[j])){ 10 flag=1; break; 11 } 12 if(!flag) 13 dfs(n,cur+1); 14 } 15 }
一般地,若在回溯法中修改了辅助的全局变量,则一定要及时把它恢复原状(除非故意保留所做的修改)。
1 int tot=0; 2 int vis[3][maxn]; //分别表示所在列,主对角,副对角是否已经放了元素 3 void dfs(int n,int cur){ 4 if(cur==n) ++tot; 5 for(int i=0;i<n;i++){ 6 if(!vis[0][i]&&!vis[1][i+cur]&&!vis[2][cur-i+n]){ 7 vis[0][i]=vis[1][i+cur]=vis[2][cur-i+n]=1; 8 dfs(n,cur+1); 9 vis[0][i]=vis[1][i+cur]=vis[2][cur-i+n]=0; 10 } 11 } 12 }
特别地,若函数有多个出口,则在每个出口处恢复被修改的值。
分析:用vis标记每一位是否访问过了,记得最后把修改过的vis数组还原
1 #include "iostream" 2 #include "cstdio" 3 #include "cstring" 4 using namespace std; 5 const int maxn=25; 6 int a[maxn]; 7 int vis[maxn]; 8 int n; 9 bool prime(int x){ 10 if(x==2) 11 return true; 12 for(int i=2;i*i<=x;i++) 13 if(x%i==0) 14 return false; 15 return true; 16 } 17 void dfs(int n,int cur){ 18 if(cur==n&&prime(a[0]+a[n-1])){ 19 for(int i=0;i<n-1;i++) 20 printf("%d ",a[i]); 21 printf("%d ",a[n-1]); 22 } 23 else{ 24 for(int i=2;i<=n;i++){ 25 if(!vis[i]&&prime(a[cur-1]+i)){ 26 a[cur]=i; 27 vis[i]=1; 28 dfs(n,cur+1); 29 vis[i]=0; 30 } 31 } 32 } 33 } 34 int main() 35 { 36 int cnt=0; 37 while(cin>>n) 38 { 39 if(cnt++) 40 printf(" "); 41 printf("Case %d: ",cnt); 42 memset(a,0,sizeof(a)); 43 memset(vis,0,sizeof(vis)); 44 a[0]=1; 45 vis[1]=1; 46 dfs(n,1); 47 } 48 return 0; 49 }
分析:问题的关键在于如何判断当前字符串是否已经存在连续子串。跟八皇后类似,我们可以只去判断当前串的后缀,而不需要去判断所有子串。看后一半是否等于前一半,这样可以省去很多不必要的判断。
1 #include "iostream" 2 #include "cstdio" 3 #include "cstring" 4 using namespace std; 5 const int maxn=110; 6 int s[maxn]; 7 int n,l; 8 int cnt; 9 bool dfs(int cur){ 10 if(cnt++==n){ 11 for(int i=0;i<cur;i++){ 12 if(i%64==0&&i) 13 printf(" "); 14 else if(i%4==0&&i) 15 printf(" "); 16 printf("%c",'A'+s[i]); 17 } 18 printf(" "); 19 printf("%d ",cur); 20 return false; 21 } 22 for(int i=0;i<l;i++){ 23 int ok=0; 24 s[cur]=i; 25 for(int j=1;2*j<=cur+1;j++){ //枚举2*j长度的后缀 26 int flag=0; 27 for(int k=0;k<j;k++){ //判断是否出现重复子串 28 if(s[cur-k]!=s[cur-j-k]){ 29 flag=1; break; 30 } 31 } 32 if(!flag){ //出现重复子串,不满足 33 ok=1; break; 34 } 35 } 36 if(!ok){ 37 if(!dfs(cur+1)) return false; 38 } 39 } 40 return true; 41 } 42 int main() 43 { 44 while(cin>>n>>l) 45 { 46 if(!n&&!l) break; 47 memset(s,0,sizeof(s)); 48 cnt=0; 49 bool flag=dfs(0); 50 } 51 }