ACM_回溯法总结

回溯:把问题分解成若干个步骤并且递归求解时,如果当前步骤没有合法的选择,则返回上一级递归调用。

优点:回溯法在递归中边生成边检查,从而减少了大量不必要的枚举量

  • 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;
    }



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值