题目:我是超链接
题意:裸的K短路(s到t第k短的路)。题目大意就是给出一个图,然后给出一个起点个一个终点,求这两点间的第K短路。
题解:
如何求第k短呢?一种简单的方法是广搜,记录t出队列的次数,第k次出队的时候,就是第k短路,但这样搜索时空复杂度很高
A*是一种常见的优化,启发式搜索,它可以用公式表示为f[n]=g[n]+h[n],f[n]表示从s经节点n到t的估价函数,g[n]是在s到n的实际代价,h[n]是从n到t的最佳路径估计代价。在设计中,要保证h[n]<=n到t的实际代价(宁愿估小了遍历一遍都不要估大了),h[n]越接近真实值,速度++
算法过程:
1.反向连边,目的是求所有点到t的最短路,用dis[i]表示i到t的最短路,其实这就是A*的启发函数,显然:h(n)<= n到t的实际代价。
2.初始化状态。状态中存放当前到达的点i,fi,gi,显然,f[i]=g[i]+dis[i],存入优先队列中
3.状态转移,转移到相邻的点
4.终止条件:每个节点最多入队列k次,当t出队列k次的时候,找到解
代码:
#include <cstdio>
#include <cstring>
#include <queue>
#define INF 1e9
#define M 100005
#define N 1005
using namespace std;
struct hh
{
int f,g,i;
bool operator <(const hh &a)const{return a.f<f;}
};
int tot,nxt[M],point[N],v[M],c[M],dis[N],tot1,nxt1[M],point1[N],v1[M],c1[M],s,t,k,cnt[N];
bool vis[N];
void addline(int x,int y,int t){++tot; nxt[tot]=point[x]; point[x]=tot; v[tot]=y; c[tot]=t;}
void addline1(int x,int y,int t){++tot1; nxt1[tot1]=point1[x]; point1[x]=tot1; v1[tot1]=y; c1[tot1]=t;}
void spfa()
{
queue <int> q;
memset(vis,0,sizeof(vis));
memset(dis,0x7f,sizeof(dis));
dis[t]=0;
q.push(t);
while (!q.empty())
{
int now=q.front(); q.pop(); vis[now]=false;
for (int i=point1[now];i;i=nxt1[i])
if (dis[v1[i]]>dis[now]+c1[i])
{
dis[v1[i]]=dis[now]+c1[i];
if (!vis[v1[i]]){q.push(v1[i]); vis[v1[i]]=true;}
}
}
}
int A_star(int s)
{
priority_queue<hh>q;
if (dis[s]>INF) return -1;
memset(vis,0,sizeof(vis));
q.push((hh){dis[s],0,s});
while (!q.empty())
{
hh now=q.top(),vv; q.pop();
cnt[now.i]++;
if (cnt[t]==k) return now.f;
if (cnt[now.i]>k) continue;
for (int i=point[now.i];i;i=nxt[i])
{
vv.i=v[i]; vv.g=now.g+c[i]; vv.f=vv.g+dis[v[i]];
q.push(vv);
}
}
return -1;
}
int main()
{
int m,n,i;
scanf("%d%d",&n,&m);
for (i=1;i<=m;i++)
{
int x,y,t;
scanf("%d%d%d",&x,&y,&t);
addline1(y,x,t);
addline(x,y,t);
}
scanf("%d%d%d",&s,&t,&k);
if (s==t) k++;
spfa();
printf("%d",A_star(s));
}