zoukankan      html  css  js  c++  java
  • 搜索问题总结

    剪枝技巧

    • P1731生日蛋糕

      一道专门考察剪枝的搜索题,在这里我把一些常用的剪枝手段进行一些总结:

      1. 有时候先对所有的东西进行排序,然后再从大的开始搜索,剪枝效果更明显,原因是比较大的在选择之后变数较小,即在比较靠近根的位置进行剪枝

      2. 在搜索有序的时候,可以记录上一次选了什么东西,这样有时候可以确定下一次枚举的起始位置,也有时候可以减少出现相邻两次操作效果抵消的问题

      3. 利用极限进行剪枝,比如把xx都按最大算,或者把xx都按最小算,如果这样都不行,就cut掉(可行性和最优性都有)

      4. 循环+dfs时,每轮循环完了之后,可以判断一下是不是已经把答案搜出来了,如果搜出来了,就return

      5. 桶有时候可以优化我们的搜索

      6. A*算法使用时,为了减少priority_queue的调整,可以用大于等于/小于等于号。

      7. 一些数学公式预测未来的行为(比如求和公式等)

      本题代码如下:

      #include <cstdio>
      #include <algorithm>
      #include <cmath>
      #define ll long long
      #define INF 999999999
      using namespace std;
      ll n,m,s=INF;
      void dfs(ll step,ll r,ll h,ll sum,ll cost);
      int main(){
      	ll r,h;
      	scanf("%lld %lld",&n,&m);
      	for(r=sqrt((n/(m)))+1;r>=m;r--){ //先搜大的再搜小的 
      		for(h=n/((m)*(m))+1;h>=m;h--){
      			dfs(1,r,h,2*r*h+r*r,r*r*h);
      		}
      	}
      	if(s==INF){
      		printf("0
      ");
      		return 0;
      	} 
      	printf("%lld
      ",s);
      	return 0;
      }
      void dfs(ll step,ll r,ll h,ll sum,ll cost){
      	ll x=m-step;
      	if(sum+(x*(x+1)*(2*x+1))/3>s){ //最优性剪枝,后面的按照1^2+2^2+……的最小情况算 
      		return;
      	}
      	if(cost+x*(r-1)*(r-1)*(h-1)<n){ //最大也拼不成 
      		return;
      	}
      	if(cost+(x*x*(x+1)*(x+1))/4>n){ //可行性剪枝,这样不够拼成蛋糕 ,负优化?? 
      		return;
      	}
      	if(step==m){
      		if(cost==n){
      			s=min(s,sum);
      			return;
      		} else {
      			return;
      		}
      	} else {
      		if(cost>=n){ //体积超了 
      			return;
      		} else {
      			if(r<=0 || h<=0){ //前面用多了 
      				return ;
      			} else {
      				for(ll i=r-1;i>=m-step;i--){ //枚举确保能够拼下去
      					for(ll j=h-1;j>=m-step;j--){
      						dfs(step+1,i,j,sum+2*i*j,cost+i*i*j);
      					}
      				}
      			}
      		}
      	}
      }
      

    启发式搜索

    • P2324 骑士精神

      这道题地图太大了,状压都压不住,所以我们得考虑用dfs之类的搜索方法去做。但是裸的dfs又不行,所以考虑一下启发式迭代加深搜索,启发函数挺好写的,就是不在目标位置上的点的个数/2。然后就直接用启发函数剪枝就好了。另外,为了更有效的剪枝,我们需要在深搜的时候,避免上一次和这一次的移动抵消了,所以dfs的时候我们最好传一下上一次进行了什么操作这样一个参数。事实上,这个剪枝很重要,没有它的话即使是IDA*也没法通过此题(只有20pts)。

      AC代码如下:

      #include <cstdio>
      #include <algorithm>
      #include <cstdlib>
      #include <cstring>
      #define ll long long
      using namespace std;
      ll t,flag=0;
      char graph[7][7];
      char goal[7][7];
      int dx[8]={-1,-1,2,2,-2,-2,1,1}; //对称式存储方法比较好判断抵消的情况
      int dy[8]={-2,2,-1,1,-1,1,-2,2};
      ll evaluate();
      void dfs(ll step,ll maxstep,ll x,ll y,ll last);
      int main(){
      	scanf("%lld",&t);
      	for(int i=0;i<5;i++){
      		for(int j=0;j<i;j++){
      			goal[i][j]='0';
      		}
      		for(int j=i;j<5;j++){
      			goal[i][j]='1';
      		}
      	}
      	goal[2][2]='*';
      	goal[3][3]='0';
      	goal[4][4]='0';
      	for(int i=0;i<t;i++){
      		for(int j=0;j<5;j++){
      			scanf("%s",graph[j]);
      		}
      		flag=0;
      		ll x,y;
      		for(ll j=0;j<5;j++){
      			for(ll k=0;k<5;k++){
      				if(graph[j][k]=='*'){
      					x=j;
      					y=k;
      				}
      			}
      		}
      		for(ll j=1;j<=15;j++){
      			if(flag) break;
      			dfs(0,j,x,y,8);
      		}
      		if(flag==0){
      			printf("-1
      ");
      		}
      	}
      	return 0;
      }
      void dfs(ll step,ll maxstep,ll x,ll y,ll last){
      	ll ok=1;
      	if(flag) return;
      	ll value=evaluate();
      	if(step+value>maxstep) return;
      	if(step==maxstep) {
      		for(int i=0;i<5;i++){
      			for(int j=0;j<5;j++){
      				if(graph[i][j]!=goal[i][j]){
      					ok=0;
      					break;
      				}
      			}
      			if(ok==0)
      				break;
      		}
      		if(ok){ //已经找到了 
      			flag=1;
      			printf("%lld
      ",maxstep);
      			return;
      		}
      		return;
      	} else {
      		for(int i=0;i<8;i++){
      			ll xx=x+dx[i],yy=y+dy[i];
      			if(xx>=0 && xx<5 && yy>=0 && yy<5 && last+i!=7){ //一定不要和上一次抵消了!
      				swap(graph[xx][yy],graph[x][y]);
      				dfs(step+1,maxstep,xx,yy,i);
      				swap(graph[xx][yy],graph[x][y]);
      			}
      			if(flag) return;
      		}
      	}
      }
      ll evaluate(){
      	ll value=0;
      	for(int i=0;i<5;i++){
      		for(int j=0;j<5;j++){
      			if(graph[i][j]!=goal[i][j]){
      				value++;
      			}
      		}
      	}
      	return value/2;
      }
      
    • P5507 机关

      题目大意见原题。

      这个题是我做过的第一道启发式搜索的题目。在做这个题目之前,我们不难想到可以用双向广搜去做这个题,并且大概率能通过。但是,我这个题不会写双向广搜啊,所以就得另寻他路了。之前以为启发式很玄,但其实拿这个例子来讲,或者用八数码的例子讲,那些抽象的概念就变得容易理解多了。这道题用IDA(*)是过不了的,毕竟它的时间消耗还是挺大的,为了通过这道题,我们需要用A(*) 算法。对于这道题,就是用bfs,只不过在bfs的过程中,所有入队的状态都要先算一下它的启发函数和已经走过的距离,并且队列要用优先队列,这样能保证那些更有希望走到终点的状态先被搜到。注意不能只按照启发函数大小确定优先级,还有原先已经走过的步数!值得注意的是,这里利用了剪枝技巧中的6号技巧,这样能减少堆的调整(您可能会发现我的代码如果不用这个技巧就会TLE,用的话会跑得飞快)。整个题从裸的bfs,到双向bfs,再到IDA(*)和A(*),我写了一整天才通过,好艰难!

      A(*)代码如下:

      #include <bits/stdc++.h>
      #define ll long long
      using namespace std;
      typedef struct{
      	int state;
      	int c[5];
      }Node;
      typedef struct{
      	int allstate;
      	int value;
      	int step;
      	int path[20];
      }AllState;
      Node node[13];
      int sign[1<<24];
      priority_queue<AllState> q;
      AllState bfs();
      int evaluate();
      int getHashValue();
      void setState(AllState s);
      int main(){
      	for(int i=1;i<=12;i++){
      		scanf("%d%d%d%d%d",&node[i].state,&node[i].c[1],&node[i].c[2],&node[i].c[3],&node[i].c[4]);
      		node[i].state--;
      	}
      	AllState ans=bfs();
      	printf("%d
      ",ans.step);
      	for(int i=1;i<=ans.step;i++)
      		printf("%d ",ans.path[i]);
      	return 0;
      }
      AllState bfs(){
      	AllState start;
      	start.allstate=getHashValue();
      	start.step=0;
      	start.path[0]=0;
      	start.value=evaluate();
      	q.push(start);
      	sign[start.allstate]=1;
      	while(!q.empty()){
      		AllState now=q.top();
      		q.pop();
      		setState(now);
      		if(now.value==0){
      			return now;
      		}
      		for(int i=1;i<=12;i++){
      			int i_state=node[i].state;
      			int follow=node[i].c[i_state+1];
      			int follow_state=node[follow].state;
      			if(i_state==0 && follow_state==0) continue;
      			node[i].state=(node[i].state+1)%4;
      			node[follow].state=(node[follow].state+1)%4;
      			AllState newstate;
      			newstate.allstate=getHashValue();
      			newstate.value=evaluate();
      			newstate.step=now.step+1;
      			for(int j=1;j<=now.step;j++)
      				newstate.path[j]=now.path[j];
      			newstate.path[newstate.step]=i;
      			if(!sign[newstate.allstate]) {
      				sign[newstate.allstate]=1;
      				q.push(newstate);
      			} 
      			node[i].state=i_state;
      			node[follow].state=follow_state;
      		}
      	}
      	printf("fuck
      ");
      	return q.top();
      }
      int evaluate(){
      	int value=0;
      	for(int i=1;i<=12;i++){
      		if(node[i].state!=0){
      			value+=4-node[i].state;
      		}
      	}
      	return value/2;
      }
      void setState(AllState s){
      	int value=s.allstate;
      	for(int i=12;i>=1;i--){
      		node[i].state=value%4;
      		value/=4;
      	}
      }
      int getHashValue(){
      	int value=0;
      	for(int i=1;i<=12;i++)
      		value=4*value+node[i].state;
      	return value;
      }
      bool operator <(AllState a,AllState b){
      	if(a.value+a.step>=b.value+b.step) //利用了6号剪枝技巧
      		return true;
      	return false;
      }
      

    meet in the middle

    • P4799 世界冰球锦标赛

      题目大意:有一些物品,物品有价值和重量两个属性,在不超过人能承受的最大重量的情况下,求有多少种取物品的方案。

      如果题目数据小,那就是一个01背包求不同方案数的裸题。但是这个题目承受重量是1e18,太大了,不过物品却较少,<=40,所以我们可能要考虑换一种方案。显然每个物品有选与不选两个可能,如果暴力搜索,那么有(2^{40}) 种方案,太多了,根本算不完。我们可能想到了双向搜索,但是之前只写过双向广搜解决地图走路问题,这个题似乎要双向深搜?咋做呢?根据学习meet in the middle算法,我知道了这个题的解决方案:正着搜一半,反着搜另一半,我们可以把前一半的结果记下来,排个序,然后每进行完一次后一半的搜索,就在前一半中二分查找一下,看看有多少个情况能够使得重量不超,这样整合答案的时间每次是对数级的,状态数也少了。这个题目是典型的meet in the middle的思想,不难有这个思路,但是二分合并答案可能是没有想到的。另外,前一半搜索的结果被记录下来了,这是典型的以空间换时间的做法。先对结果进行sort,然后再进行其他操作,也可能能够使得后续操作免除一些麻烦(比如非负,可以去掉绝对值),或者更快(比如二分)。

      这道题特别注意的一点实现细节:我们存下来了前半部分和后半部分的状态,所以状态数组要足够大,得超过1e6吧。

      代码如下:

      #include <cstdio>
      #include <algorithm>
      #include <cstdlib>
      #define ll unsigned long long
      using namespace std;
      const int N=1e7+9;
      ll left[N],right[N],n,m,a[50],cnt,cntleft,cntright,ans;
      void dfs(ll step,ll maxstep,ll sum,ll s[]);
      ll b_search(ll l,ll r,ll s[],ll key);
      int main(){
      	scanf("%llu %llu",&n,&m);
      	for(ll i=1;i<=n;i++){
      		scanf("%llu",&a[i]);
      	}
      	dfs(1,n/2,0,left);
      	cntleft=cnt;
      	cnt=0;
      	dfs(n/2+1,n,0,right);
      	cntright=cnt;
      	sort(left+1,left+cntleft+1);
      //	for(int i=1;i<=cntleft;i++){
      //		printf("%lld ",left[i]);
      //	}
      //	printf("
      ");
      	for(ll i=1;i<=cntright;i++){
      		ll pos=b_search(1,cntleft,left,m-right[i]);
      		ans=ans+pos-1;
      	}
      	printf("%llu
      ",ans);
      	return 0;
      } 
      ll b_search(ll l,ll r,ll s[],ll key){
      	ll mid;
      	while(l<=r){
      		mid=(l+r)/2;
      		if(s[mid]<=key){
      			l=mid+1;
      		} else {
      			r=mid-1; //?
      		}
      	}
      	return l;
      }
      void dfs(ll step,ll maxstep,ll sum,ll s[]){
      	if(sum>m){
      		return;
      	}
      	if(step>maxstep){
      		if(sum<=m){
      			s[++cnt]=sum;
      			return;
      		} else {
      			return;
      		}
      	}
      	dfs(step+1,maxstep,sum+a[step],s);
      	dfs(step+1,maxstep,sum,s);
      }
      
  • 相关阅读:
    【PostgreSQL-9.6.3】触发器概述(普通触发器)
    【MySQL】二进制分发安装
    【MySQL】RPM包安装
    【PostgreSQL-9.6.3】分区表
    【PL/SQL】用星号拼出金字塔
    【PostgreSQL-9.6.3】临时表
    【PL/SQL】触发器示例:记录加薪
    【PL/SQL】九九乘法口诀表
    数据结构和算法
    类元编程
  • 原文地址:https://www.cnblogs.com/BUAA-Wander/p/13311277.html
Copyright © 2011-2022 走看看