搜索专题训练(2)

来源:互联网 发布:网络销售外汇好做吗 编辑:程序博客网 时间:2024/05/29 13:14

最开始学搜索的时候觉得dfs比较简单,现在觉得搜索里面最蛋疼的还是dfs啊。。。bfs说白了就是判个重,写熟了没多困难,A*什么的至少思考的方向比较明确吧(设计估价函数嘛)。。。dfs有各种想不到的神剪枝啊。。。(纯属我这个菜比的个人理解。)

我现在的状态是:能想到的剪枝瞬间就可以想到。。想不到的剪枝不看题解的话怎么也找不到思路。当然一般我想到的剪枝都完全不够啊。。。

还是找了一些dfs题来练习。

第一道题:noi1999 生日蛋糕

题目地址:http://www.wikioi.com/problem/1710/

题目很简洁。思路也应该比较清晰,就是dfs。。。关键看剪枝。(注意除了最大的那层,其它层增加的面积其实就是侧面积。。)

先确定每层枚举r和h的上下界:必然小于上一层(从最大的开始枚举的。。),必然大于等于还要做的层数。

然后想到简单的最值优化一下:如果目前面积已经大于当前最优解了,剪枝。

这样当然远远不够,考虑把那个最值优化加强一下 如果当前面积加上还需填层数的产生的最小面积大于当前最优解的话,剪枝。 至于这个最小面积可以预处理递推出来:考虑第一层r=h=1,第二层r=h=2。。。。

实际测试一下。。。效率依旧很低。。我们来找下原因:前面的剪枝只用到了当前面积与层数两个条件,而当前剩余体积这个重要的制约条件没有考虑进去。。。

于是我们就可以这样设计一下:当前面积+剩下体积产生的最小面积大于当前最优解的话,剪枝。 剩下体积产生的最小面积考虑到形状比较复杂,不好计算出准确值,那我们就可以退一步来想,计算出比这个最小面积更小的一个近似值(如果近似值比最小面积大剪枝就过度了,正确性不能保证)。

至于这个近似值嘛。。。把剩下的层数都当成一层,考虑表示体积的式子为r*r*h,表示面积的式子为2*r*h,那么S=2*V/R,我们带入这里最大可取的R(即比本层r小1),就可算出这个近似的最小S。

#include<cmath>#include<cstdlib>#include<cstring>#include<cstdio>#include<iostream>#include<algorithm>using namespace std;const int inf=0x3f3f3f3f;int n,m;int V[30+10],S[30+10];//分别是1~n层最少体积和最少面积。 int ans=inf;void dfs(int pos,int useV,int useS,int lasth,int lastr){if(pos==0){if(useV==n)ans=min(ans,useS);return;}if(useS+S[pos]>ans||n-useV<V[pos])return;if(useS+2*(n-useV)/lastr>=ans)return;for(int h=pos;h<=lasth-1;h++){if(useV+h*pos*pos+V[pos-1]>n)break;if(useS+2*pos*h+((pos==m)?pos*pos:0)+S[pos-1]>=ans)break;for(int r=pos;r<=lastr-1;r++){if(useV+h*r*r+V[pos-1]>n)break;int nextV=useV+h*r*r,nextS=useS+2*r*h+((pos==m)?r*r:0);if(nextS+S[pos-1]>=ans)break;dfs(pos-1,nextV,nextS,h,r);}}}void pre(){for(int i=1;i<=m;i++){V[i]=V[i-1]+i*i*i;S[i]=S[i-1]+i*i*2;}}int main(){scanf("%d%d",&n,&m);pre();dfs(m,0,0,inf,inf);printf("%d\n",ans);return 0;}

第二道题:埃及分数

题目地址:http://www.wikioi.com/problem/1288/

这道题嘛。。。搜索的深度是无限的。。直接dfs的话根本搜不完。那么考虑bfs?宽度又比较大,空间很吃紧。

这样的话就要考虑另一种搜索的思路:IDFS(即迭代加深搜索)。它是这样的:枚举深度上限,每次进行dfs,搜到深度上限就返回。这样每次枚举如果找到解,肯定就是最优解(因为深度更小上限的时候没找到解。。。当然这个最优解只是指深度最小)。这样程序比较好写,空间开销小,效率也不错。但是这样的缺点很明显:重复搜索太多,但其实搜索的复杂度随深度是指数增长的,IDFS的复杂度是其实比最后一次搜索大不了多少,所以当用IDFS对一道题有一些明显的优势时,这个缺点就可以忽略了。

IDFS一般用在最优解深度比较小,重复搜索少,而空间开销比较大的题中。。。这道题用它就非常合适。

这道题的思路就是每次枚举分母,枚举下界就是上一个确定的分母+1(保证分母递增,减少重复搜索),上界为:(深度上限-当前深度)/(目标分数-当前已有分数和),意思为就算剩下的几个可填位置都用当前的枚举分数填上,和还是不够的话,就停止枚举。

有一个很重要的地方:由于和(目标分数)是确定的,最后一个分数可以直接用 目标分数-当前已有分数和 求出来再判断。。把须枚举的深度减少1。刚才说过,搜索的复杂度是指数级别的,深度少1,自然会快不少。

当然这里要注意的是这个最后一个分数的分母依然要满足递增。。我就在这被坑了很久

#include<cstdio>#include<cmath>#include<cstdlib>#include<iostream>#include<algorithm>#include<cstring>using namespace std;const int inf=0x3f3f3f3f;int DEP;int gcd(int a,int b){if(b==0)return a;else return gcd(b,a%b);}struct fraction{long long u,d;fraction operator =(int x){u=x;d=1;return *this;}fraction operator =(const fraction &b){u=b.u;d=b.d;return *this;}bool operator <(const fraction &b)const{return u*1.0/d<b.u*1.0/b.d;}bool operator >(const fraction &b)const{return u*1.0/d>b.u*1.0/b.d;}bool operator ==(const fraction &b)const{if(u==b.u&&d==b.d)return true;else return false;}void update(){int x=gcd(d,u);d/=x;u/=x;}fraction operator +(const fraction &b){fraction c;int x=gcd(d,b.d);x=d*b.d/x;c.d=x;c.u=x/d*u+x/b.d*b.u;c.update();return c;}fraction operator -(const fraction &b){fraction c;int x=gcd(d,b.d);x=d*b.d/x;c.d=x;c.u=x/d*u-x/b.d*b.u;c.update();return c;}}aim;int stack[10000+10];long long ans[10000+10];bool flag=0;bool dfs(int dep,const fraction &now,int last){if(now>aim)return false;if(dep+1==DEP)//最后一位可直接推出 {fraction cc=aim-now;if(cc.u==1&&cc.d>last)//注意最后一位依然要满足大小关系 {stack[dep]=cc.d;if(stack[dep]<ans[dep])for(int i=0;i<DEP;i++)ans[i]=stack[i];return true;}else return false;}int down=last+1;bool ret=false;for(int i=down;;i++){fraction cc=aim-now;if(i>ans[DEP-1]||(DEP-dep)*cc.d<cc.u*i)break;fraction tt;tt.u=1,tt.d=i;stack[dep]=i;tt=tt+now;if(tt>aim)continue;if(dfs(dep+1,tt,i))ret=true;}return ret;}int main(){memset(ans,0x3f,sizeof(ans));scanf("%lld%lld",&aim.u,&aim.d);aim.update();fraction xx;xx.u=0;xx.d=1;for(DEP=1;DEP<=2000;DEP++){if(dfs(0,xx,0)){printf("%lld",ans[0]);for(int i=1;i<DEP;i++)printf(" %lld",ans[i]);printf("\n");return 0;}}return 0;}

第三道题:hdu 1760 A New Tetris Game

这是一道博弈的题目,要用到的是博弈搜索。。。

博弈这种东西,基本上算是AI的基础吧,而且貌似经济学也会涉及到。。。

博弈题目都有一个很明显的特点:两人或多人玩什么游戏,每个人都非常聪明,采取最优策略,问最后某人能不能赢。。。

其实最后的输赢情况肯定是唯一的,如果数据范围比较小可以完全搜出来,如果数据范围比较大,虽然答案也是唯一的,但是不能在短时间内出解。。所以一般采取搜索+估计什么的来取较优解。。。

这道题数据非常的小。。。直接搜索,不加任何剪枝都可以过。。

博弈搜索一般是这样的:函数值代表己方得分-对方得分。。轮到己方做决策时,就要尽量找一个是这个值更大的决策,相反,对方做决策时就要尽量让它小。。

设己方节点为max节点,对方节点为min节点  那么max节点的ret值为max{子节点值} (因为一般是双方轮流做决策,max节点的子节点一般就是min节点啦,min节点的子节点又是max节点),min节点值=min{子节点值}。

这里有一个比较著名的剪枝:alpha-beta 剪枝。

如果当前节点为max节点,其值已经被一些子节点更新到A了,如果A>其父节点当前已经被更新到的值B,那么本节点剩下的子节点就不用继续搜了(继续搜只可能会让本节点更大,而父节点是min节点,取最小值,当前节点再更新不会对父节点起任何作用了)当前节点为min节点是同理。。。

本题因为比较水。。。就不做具体分析了。

#include<cstdio>#include<cstdlib>#include<cmath>#include<cstdlib>#include<iostream>#include<algorithm>using namespace std;const int inf=0x3f3f3f3f;int n,m;char map1[50+10][50+10];int get_max(int fa);int get_min(int fa){int ret=inf;for(int i=0;i<n-1;i++){for(int j=0;j<m-1;j++){if(!map1[i][j]&&!map1[i+1][j]&&!map1[i][j+1]&&!map1[i+1][j+1]){map1[i][j]=map1[i+1][j]=map1[i][j+1]=map1[i+1][j+1]=1;ret=min(ret,get_max(ret));map1[i][j]=map1[i+1][j]=map1[i][j+1]=map1[i+1][j+1]=0;}if(ret<fa)break;}}return ret;}int get_max(int fa){int ret=-inf;for(int i=0;i<n-1;i++){for(int j=0;j<m-1;j++){if(!map1[i][j]&&!map1[i+1][j]&&!map1[i][j+1]&&!map1[i+1][j+1]){map1[i][j]=map1[i+1][j]=map1[i][j+1]=map1[i+1][j+1]=1;ret=max(ret,get_min(ret));map1[i][j]=map1[i+1][j]=map1[i][j+1]=map1[i+1][j+1]=0;}if(ret>fa)break;}}return ret;}int main(){while(scanf("%d%d",&n,&m)!=EOF){for(int i=0;i<n;i++){scanf("%s",map1[i]);for(int j=0;j<m;j++)map1[i][j]-='0';}if(get_max(inf)>0)printf("Yes\n");else printf("No\n");}return 0;}

还做了一些题感觉没多大价值专门写出来。。就算了吧

0 0