启发式搜索和迭代深搜两道模板题
以前不会这两种搜索,而且在来长沙之前根本就不知道有这两个东西。
现在终于打过模板题了,知道是什么东西了,好开心。
其实启发式搜索和迭代深搜有一个共同特点就是基本上都需要估价函数,启发式搜索比迭代深搜要难理解一些。
迭代深搜就是限制了深度的搜索,因为深度限制了,所以很多东西处理起来更方便,除了搜索的深度控制住了,很多时候宽度也可以减小。
一般适用于深度无限制,或需要最小深度可行解的问题。而且空间开销小且利于剪枝。
启发式搜索就是一个让我感觉很神奇的东西了。
具体步骤是这样的:
首先,我们定义F = G + H
对于每个点,都有自己的G、H、F。其中G表示从特定的点到起点的距离,H表示从该点到目标的估值,那么F就是经过该点路径的估值。
1、把起点加入到openlist中
2、重复以下步骤
a、从openlist中找出F最小的节点,并把它当做当前的操作节点
b、检查当前点周围的点,如果已经在openlist中看是否能通过当前点得到更小的G,如果能就更新那个点的G,F的值,如果在closelist中或者是障碍物(不可达)则忽略他们
c、把当前点从openlist中移除 ,加入closelist中
d、当目标点加入closelist中时停止
3、保存路径,从目标点出发,按照父节点指针遍历,直到找到起点。
然后是两道模板题还有代码:
启发式搜索:八数码问题
代码:
//Serene #include<algorithm> #include<iostream> #include<cstring> #include<cstdlib> #include<cstdio> #include<cmath> #include<queue> using namespace std; const int maxn=362880+20,INF=0x3f3f3f3f; int T,n=9,a[12],b[12],c[12],mi[20],to=1,H[maxn],F[maxn],now; bool usd[12]; int aa;char cc; int read() { aa=0;cc=getchar(); while(cc<'0'||cc>'9') cc=getchar(); while(cc>='0'&&cc<='9') aa=aa*10+cc-'0',cc=getchar(); return aa; } struct Node{ int id; Node(){} Node(int id):id(id){} bool operator < (const Node& b) const{ return F[id]+H[id] > F[b.id]+H[b.id]; } }; priority_queue<Node> G; int get_id(int* f) { int tot,rs=0; for(int i=1;i<n;++i) { tot=0; for(int j=i+1;j<=n;++j) if(f[i]>f[j]) tot++; rs+=tot*mi[n-i]; } return rs+1; } int get_F(int* f) { int x,xx,yy,xxx,yyy,rs; for(int i=1;i<=n;++i) { x=f[i]; xx=(i-1)/3; yy=i-xx*3; xx++; xxx=(x-1)/2; yyy=x-xxx*3; xxx++; rs+=abs(xx-xxx)+abs(yy-yyy); } return rs; } void get_b(int x) { memset(c,0,sizeof(c)); x--; memset(usd,0,sizeof(usd)); for(int i=1;i<n;++i) { c[i]=x/mi[n-i]; x-=c[i]*mi[n-i]; } for(int i=1;i<=n;++i) { x=c[i]+1; for(int j=1;j<=n;++j) if(!usd[j]&&(--x)==0) { b[i]=j; usd[j]=1; break ; } } } void add(int *f) { int x=get_id(f); H[x]=min(H[x],now); if(F[x]==-1){ F[x]=get_F(f); G.push(Node(x)); } } void s() { int x,y,xx,yy; memset(F,-1,sizeof(F)); memset(H,0x3f3f3f3f,sizeof(H)); while(!G.empty()) G.pop(); now=0;add(a); while(!G.empty()) { x=G.top().id; G.pop(); get_b(x); now=H[x]+1; if(x==1) return ; for(int i=1;i<=n;++i) if(b[i]==9) { y=i; break; } xx=(y-1)/3; yy=y-xx*3; xx++; if(xx!=1) { swap(b[y-3],b[y]); add(b); swap(b[y-3],b[y]); } if(xx!=3) { swap(b[y+3],b[y]); add(b); swap(b[y+3],b[y]); } if(yy!=1) { swap(b[y-1],b[y]); add(b); swap(b[y-1],b[y]); } if(yy!=3) { swap(b[y+1],b[y]); add(b); swap(b[y+1],b[y]); } } } int main() { T=read(); while(T--) { for(int i=1;i<=9;++i) { a[i]=read(); if(!a[i]) a[i]=9; } mi[0]=0;mi[1]=1; for(int i=2;i<=9;++i) mi[i]=i*mi[i-1]; s(); H[1]!=INF? printf("%d\n",H[1]) : printf("No Solution!\n"); } return 0; } /* 3 1 2 3 4 5 6 7 8 0 1 2 3 4 5 6 8 7 0 8 0 1 5 7 4 3 6 2 */
迭代深搜:埃及分数
描述
在古埃及,人们使用单位分数的和(形如1/a的, a是自然数)表示一切有理数。如:2/3=1/2+1/6,但不允许2/3=1/3+1/3,因为加数中有相同的。对于一个分数a/b,表示方法有很多种,但是哪种最好呢?首先,加数少的比加数多的好,其次,加数个数相同的,最小的分数越大越好。
如:19/45=1/3 + 1/12 + 1/180
19/45=1/3 + 1/15 + 1/45
19/45=1/3 + 1/18 + 1/30,
19/45=1/4 + 1/6 + 1/180
19/45=1/5 + 1/6 + 1/18.
最好的是最后一种,因为1/18比1/180,1/45,1/30,1/180都大。
给出a,b(0<a<b<1000),编程计算最好的表达方式。
输入:a b
输出:若干个数,自小到大排列,依次是单位分数的分母。
样例1
样例输入1
19 45
样例输出1
5 6 18
限制
各个测试点1s
代码:
//Serene #include<algorithm> #include<iostream> #include<cstring> #include<cstdlib> #include<cstdio> #include<cmath> using namespace std; #define ll long long const int maxn=1000+10; ll a,b,ans[maxn],now[maxn],dep; bool ok; ll gcd(ll x,ll y) { return y? gcd(y,x%y):x; } void dfs(ll x,ll y,ll d) { if(d>dep) return; if(y%x==0&&y/x>now[d-1]) { now[d]=y/x; if(!ok||now[d]<ans[d]) memcpy(ans,now,sizeof(now)); ok=1; return ; } ll l=max(y/x,now[d-1]+1),r=(dep-d+1)*y/x,z; if(ok&&r>=ans[dep]) r=ans[dep]-1; for(ll i=l;i<=r;++i) { now[d]=i; z=gcd(i,y); z=y*i/z; dfs(x*z/y-z/i,z,d+1); } } int main() { scanf("%lld%lld",&a,&b); ll x=gcd(a,b); a/=x; b/=x; now[0]=1; for(dep=1;dep<=1000;++dep) { dfs(a,b,1); if(ok) { for(int i=1;i<=dep;++i) printf("%lld ",ans[i]); break; } } return 0; }