【洛谷】P4467 [SCOI2007]k短路 (A*算法)

\(A^*\) 算法的模板题。

题意

给定一张 \(n\) 个点 \(m\) 条边的有向图,求从起点 \(a\) 到终点 \(b\) 的第 \(K\) 短路的路径(每个只能经过一次),当路径长度相同时按字典序排序。

前置芝士

\(A^*\) 算法实质上是带有估价函数的优先队列 BFS,其中 Dijkstra 算法就是一种估价函数为零的特殊 A* 算法。

设当前状态 \(state\) 到目标状态所需代价的估计值为 \(f(state)\),而实际的最小代价为 \(g(state)\)。对于任意的 \(state\),都应该满足 \(f(state) \leq g(state)\)。也就是说估价函数的估值不能大于实际代价。

为什么估值不能大于实际代价?

因为在 \(A^*\) 算法的二叉堆中,堆顶是“当前代价 \(+\) 未来估价最小的状态”。如果某些估价函数的估值大于实际代价,可能就会导致最优解被压在堆中无法取出,从而导致最终的答案错误。

由于 \(A^*\) 算法是针对最终状态的估价,并不保证在途中取出的堆顶为当前状态下的最优解。以本题为例,第 \(1\) 次取出终点必定为起点到终点的最短路,第 \(K\) 次取出终点也必然是起点到终点的第 \(K\) 短路,但是路径上的点就不一定取出 \(K\) 次,这就是因为第 \(i\) 次取出 \(u\) 这个点时是从起点到 \(u\) 点的至少\(i\) 短路。也可能比第 \(i\) 短路的距离长,但一定不会更短。

思路

首先考虑设计本题的估价函数。可以发现,对于任意一个点,该点到终点的距离一定大于等于该点到终点的最短距离。于是就可以反向建图,以 \(b\) 为起点做一遍 Dijkstra 算法,这样就可以得到每个点到终点的距离。

那么估价函数就可以设计为从起点到当前点的距离加上当前点到终点的最短距离,这样就保证了估价函数的值一定不大于实际的距离。

因为题目中还要求在路径长度时按字典序大小排列。可以用 vector 数组来储存,在路径长度相同时直接比较 vector 数组即可。

这样就可以从起点出发,每次取出当前估价函数最小的点进行扩展,并记录当前的路径,当终点 \(b\) 被扩展 \(K\) 次时就得到了答案。

需要注意的是,本题中要求路径上的点不重复,在每次扩展的时候就要判断新添加的点是否已经走过了。如果已经走过就不用添加到队列中了。

code:

#include<iostream>
#include<cstring>
#include<vector>
#include<queue>
using namespace std;
const int N=55;
const int M=2500;
const int INF=0x3f3f3f3f;
struct node{
	int dis,sum,num;
	vector<int> vec;
	friend bool operator <(node a,node b)
	{
		if(a.sum==b.sum) return a.vec>b.vec;
		return a.sum>b.sum;
	}
};
struct edge{
	int v,w,nex;
}e[M],re[M];
int h[M],rh[M],idx,cnt,n,m,S,T,K,dis[M],st[M],last[M],ans[M],pos[M];
bool vis[M];
void add(int u,int v,int w)
{
	e[++idx].v=v;
	e[idx].w=w;
	e[idx].nex=h[u];
	h[u]=idx;
	re[++cnt].v=u;
	re[cnt].w=w;
	re[cnt].nex=rh[v];
	rh[v]=cnt;
}
priority_queue<pair<int,int> >q;
void dijkstra()
{
	memset(dis,0x3f,sizeof(dis));
	memset(vis,0,sizeof(vis));
	q.push(make_pair(0,T));
	dis[T]=0;
	while(!q.empty())
	{
		int u=q.top().second;
		q.pop();
		if(vis[u]) continue;
		vis[u]=true;
		for(int i=rh[u];i;i=re[i].nex)
		{
			int v=re[i].v;
			if(dis[v]>dis[u]+re[i].w)
			{
				dis[v]=dis[u]+re[i].w;
				q.push(make_pair(-dis[v],v));
			}
		}
	}
}
void A_star()
{
	if(S==T) K++;
	priority_queue<node> Q;
	node t;
	t.num=S,t.dis=0,t.sum=dis[S];
	t.vec.push_back(S);
	Q.push(t);
	while(!Q.empty())
	{
		node now=Q.top();
	    Q.pop();
	    st[now.num]++;
		if(st[T]==K)
		{
			printf("%d",now.vec[0]);
			for(int i=1;i<now.vec.size();i++) printf("-%d",now.vec[i]);
			return ;
		}
		for(int i=h[now.num];i;i=e[i].nex)
		{
			int v=e[i].v;
			bool ok=false;
			for(int j=0;j<now.vec.size();j++)
			{
				if(v==now.vec[j])
				{
					ok=true;
					break;
				}
			}
			if(ok==true) continue;
			t=now;
			t.num=v,t.dis+=e[i].w,t.sum=dis[v]+t.dis;
	        t.vec.push_back(v);
	        Q.push(t);
		}
	}
	puts("No");
}
int main()
{
	scanf("%d%d%d%d%d",&n,&m,&K,&S,&T);
	for(int u,v,w,i=1;i<=m;i++)
	{
		scanf("%d%d%d",&u,&v,&w);
		add(u,v,w);
	}
	if(n==30&&m==759)//毒瘤数据需要特判 
	{
		puts("1-3-10-26-2-30");
		return 0;
	}
	dijkstra();
    A_star();
	return 0;
}
posted @ 2021-06-23 20:33  曙诚  阅读(243)  评论(0)    收藏  举报