oi-wiki
Version:
wiki for OI / ACM-ICPC
175 lines (149 loc) • 5.18 kB
Markdown
A\* 算法是 [BFS](/search/bfs) 的一种改进。
定义起点 $s$,终点 $t$。
从起点(初始状态)开始的距离函数 $g(x)$。
到终点(最终状态)的距离函数 $h(x), h*(x)$。
定义每个点的估价函数 $f(x)=g(x)+h(x)$。
A\* 算法每次从 **优先队列** 中取出一个 $f$ 最小的,然后更新相邻的状态。
如果 $h\leq h*$,则 A\* 算法能找到最优解。
上述条件下,如果 $h$ **满足三角形不等式,则 A\* 算法不会将重复结点加入队列**。
其实…… $h=0$ 时就是 [DFS](/search/dfs) 算法,$h=0$ 并且边权为 $1$ 时就是 [BFS](/search/BFS)。
## 例题 [八数码](https://www.luogu.org/problemnew/show/P1379)
题目大意:在 $3\times 3$ 的棋盘上,摆有八个棋子,每个棋子上标有 1 至 8 的某一数字。棋盘中留有一个空格,空格用 0 来表示。空格周围的棋子可以移到空格中,这样原来的位置就会变成空格。给出一种初始布局和目标布局(为了使题目简单, 设目标状态为
123
804
765
),找到一种从初始布局到目标布局最少步骤的移动方法。
$h$ 函数可以定义为,不在应该在的位置的数字个数。
容易发现 $h$ 满足以上两个性质,此题可以使用 A\* 算法求解。
代码实现:
```cpp
using namespace std;
int fx[9]={0,1,1,1,2,3,3,3,2},fy[9]={0,1,2,3,3,3,2,1,1};
int xx[4]={1,-1,0,0},yy[4]={0,0,-1,1},a[4][4]={},n,m,x,y,z,X,Y,ans;
int h()
{
int ans=0;
for(int i=1;i<=3;i++)for(int j=1;j<=3;j++)
if(a[i][j])ans+=abs(i-fx[a[i][j]])+abs(j-fy[a[i][j]]);
return ans;
}
void dfs(int tf,int X,int Y,int g)
{
int H=h();
if(!H){ans=g;return;}
if(g==tf||ans||H+g>tf)return;
for(int i=0;i<4;i++)
{
int x=X+xx[i],y=Y+yy[i];
if(x&&y&&x<=3&&y<=3)
{
swap(a[X][Y],a[x][y]);
dfs(tf,x,y,g+1);
swap(a[X][Y],a[x][y]);
}
}
}
int main()
{
for(int i=1;i<=3;i++)for(int j=1;j<=3;j++)
{
char c;scanf(" %c",&c);a[i][j]=c-'0';
if(!a[i][j])X=i,Y=j;
}
for(int i=0;;i++)
{
dfs(i,X,Y,0);
if(ans){printf("%d\n",ans);return 0;};
}
}
```
## 例题 [k 短路](https://www.luogu.org/problemnew/show/P2483)
题目大意:按顺序求一个有向图上从结点 $s$ 到结点 $t$ 的所有路径最小的前任意多(不妨设为 $k$ )个。
很容易发现,这个问题很容易转化成用 A\* 算法解决问题的标准程式。
初始状态为处于结点 $s$,最终状态为处于结点 $t$,距离函数为从 $s$ 到当前结点已经走过的距离,估价函数为从当前结点到结点 $t$ 至少要走过的距离,也就是当前结点到结点 $t$ 的最短路。
就这样,我们在预处理的时候反向建图,计算出结点 $t$ 到所有点的最短路,然后将初始状态塞入优先队列,每次取出 $f(x)=g(x)+h(x)$ 最小的一项,计算出其所连结点的信息并将其也塞入队列。当你第 $k$ 次走到结点 $t$ 时,也就算出了结点 $s$ 到结点 $t$ 的 $k$ 短路。
由于设计的距离函数和估价函数,每个状态需要存储两个参数,当前结点 $x$ 和已经走过的距离 $v$。
我们可以在此基础上加一点小优化:由于只需要求出第 $k$ 短路,所以当我们第 $k+1$ 次或以上走到该结点时,直接跳过该状态。因为前面的 $k$ 次走到这个点的时候肯定能因此构造出 $k$ 条路径,所以之后在加边更无必要。
代码实现:
```cpp
using namespace std;
const int maxn=5010;
const int maxm=400010;
const double inf=2e9;
int n,m,k,u,v,cur,h[maxn],nxt[maxm],p[maxm],cnt[maxn],ans;
int cur1,h1[maxn],nxt1[maxm],p1[maxm];
double e,ww,w[maxm],f[maxn];
double w1[maxm];
bool tf[maxn];
void add_edge(int x,int y,double z)
{
cur++;
nxt[cur]=h[x];
h[x]=cur;
p[cur]=y;
w[cur]=z;
}
void add_edge1(int x,int y,double z)
{
cur1++;
nxt1[cur1]=h1[x];
h1[x]=cur1;
p1[cur1]=y;
w1[cur1]=z;
}
struct node
{
int x;
double v;
bool operator<(node a)const{return v+f[x]>a.v+f[a.x];}
};
priority_queue<node>q;
struct node2
{
int x;
double v;
bool operator<(node2 a)const{return v>a.v;}
}x;
priority_queue<node2>Q;
int main()
{
scanf("%d%d%lf",&n,&m,&e);
while(m--)
{
scanf("%d%d%lf",&u,&v,&ww);
add_edge(u,v,ww);
add_edge1(v,u,ww);
}
for(int i=1;i<n;i++)f[i]=inf;
Q.push({n,0});
while(!Q.empty())
{
x=Q.top();Q.pop();
if(tf[x.x])continue;
tf[x.x]=true;f[x.x]=x.v;
for(int j=h1[x.x];j;j=nxt1[j])Q.push({p1[j],x.v+w1[j]});
}
k=(int)e/f[1];
q.push({1,0});
while(!q.empty())
{
node x=q.top();q.pop();
cnt[x.x]++;
if(x.x==n)
{
e-=x.v;
if(e<0){printf("%d\n",ans);return 0;}
ans++;
}
for(int j=h[x.x];j;j=nxt[j])if(cnt[p[j]]<=k&&x.v+w[j]<=e)q.push({p[j],x.v+w[j]});
}
printf("%d\n",ans);
return 0;
}
```