因为A*卡了一天QAQ

那么,A*是什么呢?

A*

A*是对于bfs的优化,启发式搜索。

例如下图:

不错,在这张图上,小人去找电脑,用bfs的话:

黄色就是bfs的搜索范围。。。不要问我为什么选黄色

Dij不会打全称

那么,A*是怎样的呢?

没看出区别?我都看不出区别

那么,这张图呢?

A*

像这种图,如果用bfs与Dij都会。。。

但是A*的启发式搜索就体现在这。

首先定义函数:

\[F(x)=G(x)+H(x)
\]

\(G(x)\)表示已经走过的路,\(H(x)\)表示期望走的路。

不难看,\(G(x)\)很容易想到,但是\(H(x)\)就十分难受了(╯﹏╰)。

例如,如果只允许你走上下左右,那么切莫雪夫距离当期望值再好不过了。

\(H(x)\)一定要比实际值小,大的话反而容易淡化\(G(x)\)的作用。

建一个小根堆,将找到的\(F(x)\)存进去,这个堆叫"OPEN"

每次取出堆顶,然后把堆顶删除,同时把他丢到"CLOSE"列表里。

我们要承偌丢到"CLOSE"列表里的数不再更新。

然后搜索上下所有四格:

已经在"CLOSE"里面的,不去理他。

不在"OPEN"里面的,加进来。

已经在“OPEN”里的,判断一下是否更优,更新。这里貌似没用

当搜到终点时,结束。

已经在“OPEN”里的,判断一下是否更优,更新。

这句话有没有用?

样例

当八个方向都可以时,就不大一样了,尤其是当走的代价不一样时,这个的作用就很明显了。

为什么终点搜到就可以退出了?大概因为\(H(x)\)都是估计\(x\)到\(终点\)的吧(而且\(H(x)\)也比真实值小的情况下)。

不过A*的具体步骤还是看情况定

那么,对于\(H(x)\)还有一个要注意的点。

首先不用说,小于实际值(否则\(G(x)\)的作用被淡化的话你就废了)。

但是如果\(G(x)\)取的不好,反而会负优化,所以\(G(x)\)尤其重要,尤其是\(G(x)\)刚好等于实际值时,A*就跑得飞快了!

而且A还有个优化,当许多\(F(x)\)都相同时,取\(G(x)\)最大大的那一个,这样能让A避免扩展了多条路径。

IDA*

BFS加迭代加深?

迭代加深是什么:

在DFS的搜索里面,可能会面临一个答案的层数很低,但是DFS搜到了另为一个层数很深的分支里面导致时间很慢,但是又卡BFS的空间,这个时候,可以限制DFS的搜索层数,一次又一次放宽搜索的层数,知道搜索到答案就退出,时间可观,结合了BFS和DFS的优点于一身。

bool  bk;
void dfs(int limit,int x)
{
if(...)
{
bk=true;
return ;
}
if(x==limit)return ;
for(...)dfs(limit,x+1);
}
for(int i=1;i<=100;i++)
{
dfs(i,0);
if(bk==true)return 0;
}

这也是个简洁的迭代加深,简称ID算法。

那么IDA=A+ID?

不是。难道12=1+2?

不过,特别舒服的事情是IDA是对与A的优化,甚至比A*还好打!

IDA*就是将\(F(x)\)搬到了IDDFS上然后加了个可行性剪枝

例题

这题目,不大难,难在了\(H(x)\)怎么做。

由于骑士要从现有状态恢复到目标状态,DFS稳了!

首先DFS不能让马走,否则状态多,应该让空格走,状态会少很多

又要求15步,IDDFS,稳了!虽然我还是傻愣愣的将0到15每次都迭代加深。

然后,我们再判断一下,在最有情况下,当一个位置的马与目标状态不一样时,那么就cnt++,期望这个马可以一个回归原位,不过在IDDFS里面的判断就不是\(if(x+ex()>limit)\)了(\(ex()\)代表期望函数)。

而是if(x+ex()>limit+1)为什么呢?因为在ex()里面我把空格也计算了一遍,而在最后一步,是可以将空格与马同时回归原位的,所以要让limit+1。

那么,代码君:

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
int st[7][7]=
{
{3,3,3,3,3,3,3},
{3,1,1,1,1,1,3},
{3,0,1,1,1,1,3},
{3,0,0,2,1,1,3},
{3,0,0,0,0,1,3},
{3,0,0,0,0,0,3},
{3,3,3,3,3,3,3}
};//目标状态
int map[7][7];//记录当前状态
inline int ex()//期望函数
{
int tot=0;
for(int i=1;i<=5;i++)
{
for(int j=1;j<=5;j++)
{
if(map[i][j]!=st[i][j])tot++;
}
}
return tot;
}
inline void wap(int &x,int &y){x^=y^=x^=y;}//交换函数swap
int dx[]={-2,-2,-1,1,2,2,1,-1};
int dy[]={-1,1,2,2,1,-1,-2,-2};//方向
bool dfs(int limit,int x,int mx,int my)
{
if(x==limit)
/*果已经到界限,判断一下,
为什么不用担心x<limit的情况呢,如果有,在上几次DFS就已经解决了。 */
{
return !ex();
}
if(x+ex()>limit+1)return false;//IDA*的可行性剪枝
for(int t=0;t<=7;t++)//空格的发个方向
{
int tx=mx+dx[t],ty=my+dy[t];//新的空格位置
if(tx<1 || tx>5 || ty<1 || ty>5)continue;//判断
wap(map[mx][my],map[tx][ty]);//交换
if(dfs(limit,x+1,tx,ty))// dfs(limit,x+1,tx,ty)==true
{
//找到了答案
wap(map[mx][my],map[tx][ty]);//交换
return true;
}
wap(map[mx][my],map[tx][ty]);//回朔
}
return false;
}
int main()
{
int T;scanf("%d",&T);
while(T--)
{
char ss[10];
int tx=0,ty=0;
for(int i=1;i<=5;i++)//构建map
{
scanf("%s",ss+1);
for(int j=1;j<=5;j++)
{
if(ss[j]=='1')map[i][j]=1;
else if(ss[j]=='0')map[i][j]=0;
else map[i][j]=2,tx=i,ty=j;//记录空格位置
}
}
bool bk=false;
for(int i=0;i<=15;i++)//0代表一开始就是目标状态
{
bk=dfs(i,0,tx,ty);
if(bk)//bk==true;
{
printf("%d\n",i);
break;//输出并break
}
}
if(!bk)printf("-1\n");//判断
}
return 0;
}

如何确定估值

估值函数的确定方法在我做完一道题目以后,突然心血来潮,发现目前做的几道题目貌似都是这样。

  1. 估值函数一般是当状态离目标越近时越优,当然是总体趋势,存在个别的。
  2. 估值函数里的参数一般是比较明显的,且每次操作后一般会改变的,一般也会满足第一点。
  3. 参数一般变化是有范围的,参考题目2。

题目

1

A*的例题(比较难)

因为luogu卡A*,给出bzoj的链接。

不就是计算k最大能是多少

反向建边,\(D(x)\)代表\(n\)到\(x\)的最短路

\[H(x)=D(x)
\]

首先有个优化,设\(kk=能量/D(1),(k≤kk)\)为什么?因为就算你条条最短路,你最多也只能扩展kk条!

那么的话呢,我们从起点开始,每次将周围的点塞进堆里面,这里什么时候丢进CLOSE列表呢,仅当一个数被访问了kk次。

而且这里一个点是可以多次被丢进OPEN列表里的,总之A*的步骤就不太一样就对了。

每次取出堆顶然后更新,当访问到终点时记录一下,然后。。。

代码君!可恶的BZOJ,卡我priority_queue,逼我手写:

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
//边目录
struct bian
{
int y,next;
double c;
}v1[210000]/*正向建图*/,v2[210000]/*反向建图*/;int last1[6000],last2[6000],vlen;
int n,m;double QWQ;//能量
inline void ins(int x,int y,double c)
{
vlen++;
v1[vlen].y=y;v1[vlen].c=c;v1[vlen].next=last1[x];last1[x]=vlen;
v2[vlen].y=x;v2[vlen].c=c;v2[vlen].next=last2[y];last2[y]=vlen;
}
//A*判断时所用
struct node
{
int d;//d表示在哪个点
double f,h;//f表示总估值,h表示实际走的长度
}now,fut;//在A*里面当中转的时候用
inline bool operator<(node x,node y){return x.f==y.f?x.h>y.h:x.f<y.f;}//重载
//手写堆。。。
node a[1510000];int len,ans=0;
inline void insert(node x)//插入
{
a[++len]=x;//放进最后一个
int i=len/2,j=len;
while(i>=1)//向上调整
{
if(a[j]<a[i])
{
node tt=a[j];a[j]=a[i];a[i]=tt;//交换
j=i;i=j/2;
}
else break;//退出
}
}
inline void del()//删除堆顶
{
a[1]=a[len];len--;//将a[len]移到堆顶,达到将a[1]删除的目的
int i=1,j=2;
while(j<=len)//向下调整
{
if((j+1)<=len && a[j+1]<a[j])j++;//找两个儿子最小的来做对比
if(a[j]<a[i])//对比
{
node tt=a[j];a[j]=a[i];a[i]=tt;//交换
i=j;j=i*2;
}
else break;//退出
}
}
//SPFA
double d[6000];
int list[6000],head,tail;
bool bol[6000];
void spfa()
{
for(int i=1;i<n;i++)d[i]=999999999.0;
head=1;tail=2;list[head]=n;bol[n]=true;//初始化
while(head!=tail)
{
int x=list[head++];bol[x]=false;
if(head==n+1)head=1;//循环队列优化
for(int k=last2[x];k;k=v2[k].next)
{
int y=v2[k].y;
if(d[x]+v2[k].c<d[y])//判断
{
d[y]=d[x]+v2[k].c;
if(!bol[y])
{
bol[y]=true;
list[tail++]=y;if(tail==n+1)tail=1;
}
//别看了,没有酸辣粉(SLF优化)
}
}
}
}
//A*
int cnt[6000];
void A_star(int kk)//计算k最大为多少
{
now.d=1;now.h=0;now.f=d[1];
insert(now);//插入起点
while(len)//len!=0,堆不为空
{
now=a[1];del();//删除堆顶
cnt[now.d]++;//计算 次数
if(now.f>QWQ)break;//最小的F值都大于能量值,只能退出了。
if(now.d==n)//到达了终点
{
ans++;
QWQ-=now.f;//消耗能量
}
if(cnt[now.d]>kk)continue;//CLOSE列表
for(int k=last1[now.d];k;k=v1[k].next)
{
fut.d=v1[k].y;fut.h=now.h+v1[k].c;fut.f=fut.h+d[fut.d];//加入新的点
insert(fut);//加入进去
}
}
}
int main()
{
scanf("%d%d%lf",&n,&m,&QWQ);
for(int i=1;i<=m;i++)
{
int x,y;double c;scanf("%d%d%lf",&x,&y,&c);
ins(x,y,c);
}
spfa();A_star(QWQ/d[1]);//一个优化
printf("%d\n",ans);//输出
return 0;
}
//强行加注释。。。

2

简单毒瘤题

这道题目我们可以认为把一段放入另外一个地方代表的是两段交换位置。

(这里的后继为每个数后面的一个数)

那么理论上来讲,最多只有三个数会改变后继。

那么在假设改变都是正确的情况下,那么最小步数就是ceil(错误的后继数量/3)。

也就是估值函数。

又回到我们确定的方法:越接近目标状态,错误的后继数量越少,每次操作都会改变,而且改变的数的绝对值为3(因为改变以后有的)。

#include<cstdio>
#include<cstring>
#define N 20
#define NN 400
using namespace std;
int qwq[NN],n;
int ex(int a[])//期望函数
{
int ans=0;
for(int i=1;i<n;i++)
{
if(a[i+1]!=a[i]+1)ans++;
}
return ans;
}
int list[NN];
void huan(int a[],int l,int r,int x)//将[l,r]插到x后面
{
for(int i=l;i<=r;i++)list[i]=a[i];
for(int i=r+1;i<=x;i++)a[i-r+l-1]=a[i];
for(int i=l+(x-r);i<=x;i++)a[i]=list[i-x+r];
}
int lim;
int dfs(int a[],int ans)//只允许把每一段插到自己后面的位置
{
int now=ex(a);
if(ans+now/3+(now%3>0)>=lim)return 5;
else if(!now)return ans;
for(int i=1;i<=n;i++)
{
for(int k=i;k<=n;k++)//找区间
{
for(int j=k+1;j<=n;j++)//找插的位置
{
memcpy(a+n+2,a+1,(n<<2));//部分copy大法好
huan(a+n+1,i,k,j);
now=dfs(a+n+1,ans+1);
if(now<lim)lim=now;
}
}
}
return lim;
}
inline int mymin(int x,int y){return x<y?x:y;}
int main()
{
int T;scanf("%d",&T);
while(T--)
{
lim=5;
scanf("%d",&n);
for(int i=1;i<=n;i++)scanf("%d",&qwq[i]);
lim=mymin(dfs(qwq,0),lim);
if(lim==5)printf("5 or more\n");
else printf("%d\n",lim);
}
return 0;
}

A*与IDA*的奇妙之旅的更多相关文章

  1. Java序列化流的奇妙之旅

    Java序列化流有何奇妙之处呢?通过一个个案例逐一感受序列化流. !!!好戏在后头!!! 1.IO流读写文件 先从一个普通文件读写字符串开始讲起. 例子:输出字符串到文件,再从文件中读取字符串 在某一 ...

  2. Java奇妙之旅day_01

    一 .java程序运行原理 1.首先我们下载JDK,它是一组命令行工具,含有编译.调试.和执行java程序所需要的软件和工具. (1)关于下载我们在这不作赘述,在Oracle官方网站直接下载,一直点击 ...

  3. 密码学奇妙之旅、01 CFB密文反馈模式、AES标准、Golang代码

    CFB密文反馈模式 CFB密文反馈模式属于分组密码模式中的一种.加密与解密使用同一结构,加密步骤生成用于异或的密钥流. 其弥补了ECB电子密码本模式的不足(明文中的重复排列会反映在密文中,通过删除替换 ...

  4. 密码学奇妙之旅、02 混合加密系统、AES、RSA标准、Golang代码

    CTR 计数器模式 计数器模式CTR是分组密码模式中的一种.通过将逐次累加的计数器进行加密来生成密钥流的流密码.每次加密时会生成一个不同的值来作为计数器的初始值. 可以事先进行加密.解密的准备. 加密 ...

  5. 密码学奇妙之旅、03 HMAC单向散列消息认证码、Golang代码

    HMAC 单向散列消息认证码 消息认证码MAC是用于确认完整性并进行认证的技术,消息认证码的输入包括任意长度的消息和一个发送者和接收者之间共享的密钥(可能还需要共享盐值). HMAC是使用单向散列函数 ...

  6. 我的mongoDb之旅(二)

    题序:上一回,咱们简单的操作了一些增减改查的操作,这一次,再来进行一场奇妙之旅 一.案例讲解 (1).字段有值与没值时的单条数据 第一条数据title这个字段(mysql用久了,习惯这么叫了)是没有数 ...

  7. 你好,C++(2)1.3 C++世界版图1.4 如何学好C++

    1.3  C++世界版图 C++语言的发展过程,不仅是一个特性不断增加.内容不断丰富的过程,更是一个在应用领域中不断攻城略地的过程.在其30余年的发展过程中,C++在多个应用领域都得到了广泛的应用和发 ...

  8. 腾讯TGideas语义化标签(转)

    --------引子--------------- 家里有个熊孩子,经常会有一些意想不到的事情发生:回家的时候,他会笑呵呵冲过来,大声喊着“臭爸爸”:你让他把鞋穿上,他会提起鞋子往楼下扔...在小孩的 ...

  9. Python入门教程(2)

    人生苦短,我玩蛇0.0! Python(英语发音:/ˈpaɪθən/), 是一种面向对象.解释型计算机程序设计语言,由Guido van Rossum于1989年底发明,第一个公开发行版发行于1991 ...

随机推荐

  1. 获取apk package name(包名)以及activity name

    通过adb 查看最上层成activity名字: linux: adb shell dumpsys activity | grep "mFocusedActivity" window ...

  2. 在浏览器的市场上,IE依然是放弃了,firefox还在继续~~

    自从微软失败后,发布了Edge:但是依然是没有多少市场的,毕竟各种难受: 单从开发者工具,我是没有其它选择了, ie的不能用,https://www.microsoft.com/en-us/welco ...

  3. 『看球笔记』20131230切尔西vs利物浦,赛后复盘聊聊球

    2013-12-30 第十九轮英超联赛 切尔西vs利物浦         看着这张板凳合照… 有木有一种心里哇凉哇凉的感觉.   赛后whoscored的平均位置图 左边车子,右边我军     阿格回 ...

  4. codeforces 676C

    C. Vasya and String time limit per test 1 second memory limit per test 256 megabytes input standard ...

  5. php 的 number_format使用

    $num = 1.0258963147; // 1.0259 $num = number_format($num, 4, '.', ''); $num = number_format($num); $ ...

  6. <知识整理>2019清北学堂提高储备D3

    全天动态规划入门到入坑... 一.总概: 动态规划是指解最优化问题的一类算法,考察方式灵活,也常是NOIP难题级别.先明确动态规划里的一些概念: 状态:可看做用动态规划求解问题时操作的对象. 边界条件 ...

  7. MariaDB修改端口号

    修改端口号 打开my.ini文件

  8. 【luogu P3371 单源最短路】 模板 vector+SPFA

    stl真是好,,偷懒少写邻接表,, 两个STL应用使代码简短了很多.然而还是那句话,天上不会掉馅饼,程序的效率还是有所下降的.然而,效率不是全部,人们宁可牺牲三倍效率用Java而不用C语言就是最好的例 ...

  9. centos安装nodejs二进制包

    1.下载nodejs的二进制包 wget https://nodejs.org/dist/v6.3.1/node-v6.3.1-linux-x64.tar.xz 2.解压下载的安装包 tar xf n ...

  10. VMware虚拟机修改BIOS启动项

    vmware默认是硬盘启动,要进bios里面设置成开机的启动顺序,要将光盘设置成第一启动项.但vm的开机画面比笔记本的还要快很多,基本都在1s内的,想进入 bios里面也有难度.. 对于网上说的开vm ...