一闪一闪亮晶晶,满天都是A*
难绷。。oj题库第一个放在A*栏的题A*根本不是正解(虽然也可做但我想先写个板子QAQ)
so...
让我们来数A*吧!
A*
定义
A* 搜索算法(A* search algorithm,A* 读作 A-star),简称 A* 算法,是一种在带权有向图上,找到给定起点与终点之间的最短路径的算法。它属于图遍历(graph traversal)和最佳优先搜索算法(best-first search),亦是 BFS 的改进。
qwq其实不似人话,让我们直接看原理。
原理
A* 算法的目标是找到有向图上从起点 \(𝑠\)到终点 \(𝑡\) 的最短路径
其依托于以下几个函数:
- \(g\) 函数:即从起点到当前点的距离
- \(g^∗\) 函数:估计的从起点到当前点的距离。由于 BFS 的特殊性,我们一定有 \(g=g^ ∗\)
- \(h\) 函数:从当前点到终点的实际距离
- \(h^∗\)函数:从当前点到终点的估计距离。
- \(f\) 函数:从起点到当前点再到终点的总距离。
即 $$f=g+h$$ - \(f^∗\)函数:从起点到当前点再到终点的估计距离。
即
搜索时,A* 算法每次从优先队列中取出一个 \(f^*\) 最小的结点。
然后,将它的所有后继结点 \(𝑥\) 都推入优先队列中,并利用实际记录的 \(𝑔(𝑥)\) 和估计的 \(ℎ^*(𝑥)\) 更新 \(𝑓^*(𝑥)\)。
tips:对于不同的题目,估价函数的计算方式是不一样的(曼哈顿距离,欧拉距离,最短路(dij,SPFA))
某机房大蛇曾说过:“你要是写博客,就不能只贺Oi Wiki,还要写上自己的理解”
所以让我们来解释一下:
烧烤其为什么是\(BFS\)的优化:
考虑一种情况:给你一个初始状态和一个目标状态,让你寻找一个最优的路径使状态转移,发现当前最优状态不一定是未来最优状态,\(BFS\)会有许多冗杂状态,而A *记录的是当前最优状态和未来估计最优状态。
A*算法有一些性质,此处不再证明,请见Oi Wiki(其实是贺不动了OvO)
算法实现
首先将当前节点加入到队列\(a\)中
接着判断队列\(a\)不为空(有起始节点),目标节点不在队列\(a\)中
然后从队列\(a\)中取出\(f\)值最小的节点(\(𝑓(𝑥)=𝑔(𝑥)+ℎ(𝑥)\)),作为当前节点,并将其加入到队列\(b\)中
计算当前节点的相邻节点的F值。若这些子节点既没有在队列\(a\)中,也没有在队列\(b\)中,所以都加入到队列\(a\)中,并把当前节点作为它们的父节点
注意:因为A*属于启发式搜索算法,每种只搜到一次可能找不到最终答案,需要进行多次查找,这也是它和一般搜索算法的不同之处。
然后,来看inf个例题;
例题1.0 八数码难题

分析
观察题目是一个网格图问题,所以估价函数 \(ℎ(𝑥)^∗\)定义为曼哈顿距离;
所以
然后通过上面的实现即可求出答案。
wait!!
发现了一个去重的问题(不去重会T的啊),以下提供1.5种解决方案:
康托展开(1)
定义
康托展开是一种将长度为 \(n\) 的排列与 \(n!\) 个整数之间建立双射关系的算法。
通俗来讲:
即给出一个全排列,求它是第几个全排列,为康托展开
给出全排列长度和它是第几个全排列,求这个全排列,叫做逆康托展开
暴力康托展开
这题只需要暴力方法的康托展开,还有线段树康托展开等参见此大佬的文章
对于一个长度为\(n\)全排列来说
若按顺序进行元素的填充,对于第\(i\)位就有\(n-i+1\)种选择
若将其看为进制,则第\(i\)位为\(n-i+1\)进制
若这一位选择了第\(k\)种元素,那么该进制填入的数为\(k\)
举例:
你有一个集合\(\{1,2,3,4,5\}\),一开始其分别对应第1,2,3,4,5种选择
为了方便起见,通常以0起下标。
考虑全排列\(a(5,2,1,4,3)\)求它是第几个全排列。
- 首位\(5\)是\(5\)种选择\(\{1,2,3,4,5\}\)的第5种,故变为4
- 次位\(2\)是\(4\)种选择\(\{1,2,3,4\}\)的第2种,故变为1
- 中间位\(1\)是\(3\)种选择\(\{1,3,4\}\)的第1种,故变为0
- 次低位\(4\)是\(2\)种选择\(\{3,4\}\)的第2种,故变为1
- 末位\(3\)是\(1\)种选择的\(\{3\}\)第1种,故变为0
最后,\(5,2,1,4,3\) 变成了\((41010)_{unknown}\)
我们看到,第\(i\)位的值就是\(a_i\)减去它左边比它小的数的数量-1
然后将\((41010)_{unknown}\)转为十进制即可
最后别忘了加一
例题1.1 Uim的情人节礼物·其之弐
洛谷 P2524 Uim的情人节礼物·其之弐
题目大意:
给你一个全排列,求它的排名
代码
#include<bits/stdc++.h>
using namespace std;
int n;
string s;
int fac[]={1,1,2,6,24,120,720,5040,40320,362880};
int a[10];
int b[10];
int contor()
{
int ans=0;
for(int i=1;i<=n;i++)
{
b[i]=a[i];
for(int j=1;j<i;j++)
{
if(a[i]>a[j])
{
b[i]--;
}
}
b[i]-=1;
}
for(int i=1;i<=n;i++)
{
ans+=b[i]*fac[n-i];
}
return ans+1;
}
int main()
{
cin>>n>>s;
for(int i=1;i<=n;i++)
{
a[i]=s[i-1]-'0';
}
cout<<contor();
return 0;
}
好!回到八个马问题(?大雾)
其实对于八个马问题,康托展开不止起去重的作用,也是对每个状态进行编号,使其更好处理
来看具体实现:
代码
#include<bits/stdc++.h>
using namespace std;
int dx[4]={0,0,1,-1};//向四个方向移动
int dy[4]={1,-1,0,0};
int fac[10]={1,1,2,6,24,120,720,5040,40320,362880};//预处理阶乘表
int dis[362881];//即函数d,已经走的步数
struct jade
{
int a[5][5],dis,h,x,y,kt;//分别为情况,走到当前状态的步数,估计函数,0所在的横坐标,0所在的纵坐标,康托展开值
bool operator <(const jade &a)const//重载运算符(用于优先队列,即堆顶是估计函数最小的那个
{
return h>a.h;
}
}ed,st;
priority_queue<jade>q;//小根堆(优先队列)
bool vis[362881];//进行去重,防止TLE
int contor(jade x)//暴力康托展开
{
int ca[10],cb[10];
int tot=0;
for(int i=1;i<=3;i++)
{
for(int j=1;j<=3;j++)
{
tot++;
ca[tot]=x.a[i][j]+1;
}
}
int ans=0;
for(int i=1;i<=9;i++)
{
cb[i]=ca[i];
for(int j=1;j<i;j++)
{
if(ca[i]>ca[j])
{
cb[i]--;
}
}
cb[i]-=1;
}
for(int i=1;i<=9;i++)
{
ans+=cb[i]*fac[9-i];
}
return ans+1;
}
struct seek
{
int x,y;
};
seek js[10];//存储目标状态的坐标(便于计算曼哈顿距离即估计函数h)
int h(jade x)//估计函数
{
int res=0;
for(int i=1;i<=3;i++)
{
for(int j=1;j<=3;j++)
{
res+=abs(js[x.a[i][j]].x-i)+abs(js[x.a[i][j]].y-j);
}
}
return res;//返回曼哈顿距离
}
int main()
{
js[1]={1,1};
js[2]={1,2};
js[3]={1,3};
js[4]={2,3};
js[5]={3,3};
js[6]={3,2};
js[7]={3,1};
js[8]={2,1};
js[0]={2,2};
ed.a[1][1]=1;
ed.a[1][2]=2;
ed.a[1][3]=3;
ed.a[2][1]=8;
ed.a[2][2]=0;
ed.a[2][3]=4;
ed.a[3][1]=7;
ed.a[3][2]=6;
ed.a[3][3]=5;
ed.x=2;
ed.y=2;//初始化目标状态
string s;
cin>>s;//初始状态输入
for(int i=1;i<=3;i++)
{
for(int j=1;j<=3;j++)
{
st.a[i][j]=s[(i-1)*3+j-1]-'0';
if(st.a[i][j]==0)
{
st.x=i;
st.y=j;
}
}
}
st.kt=contor(st);//计算康托值(即此排列是第几个全排列)
ed.kt=contor(ed);
if(st.kt==ed.kt)//相等则属于一种状态
{
cout<<0;
return 0;
}
vis[st.kt]=1;//打标记,即状态st被找到过
st.dis=0;//从st状态开始扩展
st.h=h(st);//得到估计函数的值
q.push(st);//入队
while(!q.empty())
{
jade seek=q.top();//取堆顶元素
q.pop();
if(seek.kt==ed.kt)
{
cout<<seek.dis;//找到目标状态
return 0;
}
for(int i=0;i<4;i++)//四个方向遍历
{
jade sk=seek;
int x=sk.x+dx[i];
int y=sk.y+dy[i];
if(x<1||x>3||y<1||y>3)//不合法
{
continue;
}
swap(sk.a[sk.x][sk.y],sk.a[x][y]);//将0和它旁边选中的数字互换位置
sk.x=x;
sk.y=y;
sk.kt=contor(sk);//更改状态后的康托值
if(!vis[sk.kt]||(vis[sk.kt]&&dis[sk.kt]>sk.dis))//之前没有找到这个状态||这个状态之前的步数并不是最小的
{
sk.dis++;//更新步数
dis[sk.kt]=sk.dis;
sk.h=h(sk)+sk.dis;//更改估计值(将该状态推后
vis[sk.kt]=1;
q.push(sk);
}
if(sk.kt==ed.kt)
{
cout<<sk.dis;
return 0;
}
}
}
return 0;
}
map去重&&代码实现(.5)(by:肝硬化)
提供一种使用map去重的代码(from:肝硬化)
#include <bits/stdc++.h>
const int dx[] = {0, 1, - 1, 0}, dy[] = {1, 0, 0, - 1};
char s[11];
std::string ac = "123804765";
int nx, ny, xx, yy;
inline int h(std::string s){
int as = 0;
for(int i = 0; i < 9; i ++){
if(ac[i] != s[i] && ac[i] != '0'){
as ++;
}
}
return as;
}
struct hhh{
int f, bu;
std::string nw;
bool operator <(const hhh &x)const{
return f > x. f;
}
};
std::priority_queue<hhh> q;
std::unordered_map<std::string, bool> mp;
std::unordered_map<std::string, int> dis;
inline void Astar(){
while(q. size()){
hhh x = q. top();
q. pop();
std::string t = x. nw;
if(t == "123804765"){
printf("%d", x. bu);
return ;
}
for(int i = 0; i < 9; i ++){
if(t[i] == '0'){
nx = i / 3, ny = i % 3;
}
}
int plca = nx * 3 + ny;
for(int i = 0; i < 4; i ++){
xx = nx + dx[i], yy = ny + dy[i];
if(xx < 0 || xx > 2 || yy < 0 || yy > 2){
continue;
}
int plcb = xx * 3 + yy;
std::swap(t[plca], t[plcb]);
if(mp[t] == 0 || (mp[t] == 1 && dis[t] > x. bu + 1)){
mp[t] = 1;
dis[t] = x. bu + 1;
q. push((hhh){h(t) + dis[t], dis[t], t});
}
std::swap(t[plca], t[plcb]);
}
}
return ;
}
int main(){
scanf("%s", s);
if(! h(s)){
printf("0");
return 0;
}
q. push((hhh){h(s), 0, s});
mp[s] = 1, dis[s] = 0;
Astar();
return 0;
}
例题2.0 Cow Jogging G
洛谷 P2901 [USACO08MAR] Cow Jogging G

分析
发现是k短路问题,考虑朴树(朴素)做法:
其实朴树有首很好听的歌
《Baby,До свидания》
点击查看歌词
黑夜里的站台
末班车离开
那也许是本可以拯救我的一班
背叛务必坚决
告别也需要体面
我没什么可以解释的
这是我的命运吧
我猜有个混账
在我心里面躲藏
能安慰他只有陌生还有放荡
他时刻需要对岸
无论是在哪一边
那就这样吧
我们再见了
请转身泪如雨下
当今天夕阳西下
断肠人柳巷拾烟花
我已四分五裂
从此没有了家
孤魂野鬼天涯
永远也不能到达的船
就让我沉入黑夜
Baby До свидания
街拐角的酒店
走廊尽头的房间
冰冷的床单上
有陌生人的气味
在欲望的后面
是无边的空虚悲哀
Oh亲爱的
这陌生的这城市下起雨啦
当今天夕阳西下
断肠人柳巷拾烟花
我已四分五裂
从此没有了家
孤魂野鬼天涯
永远也不能到达的船
就让我沉入黑夜
Baby До свидания
灯一幕一幕熄灭吧
我是谁我爱谁
我要谁我去哪
当今天夕阳西下
断肠人柳巷拾烟花
我已四分五裂
从此没有了家
孤魂野鬼天涯
永远也不能到达的船
就让我沉入黑夜
Baby До свидания
La la la
去哪去哪去哪
我早已没有了家
La la la
当我想起你就
让我沉入黑夜
Baby До свидания
就让我沉入黑夜
Baby До свидания
就让我沉入黑夜
Baby До свидания
朴素做法(大暴力!)
考虑找到所有路径,再进行排序,最后取最第k小值(那包 T !L !E ! 的)
所以不能写这种56做法,让我们考虑78的A*.
可可爱爱想看【数据删除】变猫娘的A*做法
观察题目是一个DAG问题,所以估价函数\(h(x)*\)定义为x到终点的距离;
所以
并且第\(k\)次到达终点就是第\(k\)短路。
具体思路
- 计算出估计函数\(h\)(dij或SPFA)
- 将BFS到的点入队并扩展
- 若找到终点则记录,找到第\(k\)次就结束程序
代码
#include<bits/stdc++.h>
using namespace std;
int n,m,k;
int dis[1010];
bool vis[1010];
int h[1010],to[10010],nxt[10010],v[10010],tot;
int _h[1010],_to[10010],_nxt[10010],_v[10010],_tot;//反向存图(方便SPFA)
struct jade
{
int pos;
long long len;
};
bool operator < (jade x,jade y)
{
return x.len+dis[x.pos]>y.len+dis[y.pos];
}
void add(int x,int y,int val)
{
tot++;
to[tot]=y;
nxt[tot]=h[x];
v[tot]=val;
h[x]=tot;
}
void _add(int x,int y,int val)
{
_tot++;
_to[_tot]=y;
_nxt[_tot]=_h[x];
_v[_tot]=val;
_h[x]=_tot;
}
void SPFA()
{
queue<int> q;
q.push(1);
memset(dis,0x3f,sizeof(dis));
memset(vis,0,sizeof(vis));
vis[1]=1;
dis[1]=0;
while(!q.empty())
{
int x=q.front();
q.pop();
vis[x]=0;
for(int i=_h[x];i;i=_nxt[i])
{
int y=_to[i];
if(dis[y]>dis[x]+_v[i])
{
dis[y]=dis[x]+_v[i];
if(vis[y]==0)
{
vis[y]=1;
q.push(y);
}
}
}
}
}
int A_star(int &ans)
{
priority_queue<jade> q;
q.push((jade){n,0});
while(!q.empty())
{
jade seek=q.top();
q.pop();
if(seek.pos==1)
{
cout<<seek.len<<endl;
ans--;
if(ans==0)
{
return 0;
}
}
int x=seek.pos;
for(int i=h[x];i;i=nxt[i])
{
int y=to[i];
q.push((jade){y,seek.len+v[i]});
}
}
return ans;
}
int main()
{
cin>>n>>m>>k;
for(int i=1;i<=m;i++)
{
int x,y,val;
cin>>x>>y>>val;
add(x,y,val);
_add(y,x,val);
}
SPFA();
A_star(k);
while(k--)
{
cout<<-1<<endl;
}
return 0;
}
胃腕黛絮...
写在最后
本文来自博客园,作者:BIxuan—玉寻,转载请注明原文链接:https://www.cnblogs.com/zhangyuxun100219/p/19139538

浙公网安备 33010602011771号