JZOJ 1163. 第K短路
题目
Description
Bessie 来到一个小农场,有时她想回老家看看她的一位好友。她不想太早地回到老家,因为她喜欢途中的美丽风景。她决定选择K短路径,而不是最短路径。
农村有 R (1≤R≤100,000) 条单向的路,每条路连接 N (1≤N≤10000) 个结点中的两个。结点的编号是 1…N。Bessie 从结点 1出发,她的朋友(目的地)在结点 N。
同一个点可以多次经过。K短路的定义:假设从1出发,有M条长度不同的路径可以到达点N,则K短路就是这M条路径中第K小的路径长度。
Input
Line 1: 三个用空格分隔的整数 N,R,K((1≤n≤10000,1≤R≤100000,1≤K≤10000)
Lines 2…R+1: 每行包含三个用空格分隔的整数x,y,len(1≤x,y≤n,1≤len≤10000),表示x到y有一条长度为len的单向道路。
Output
输出包括一行,一个整数,第K短路的长度。
Sample Input
4 4 2
1 2 100
2 4 200
2 3 250
3 4 100
Sample Output
450
题解
- 这是一道A*算法的模板题。
- 它并不是一道用A*来优化DFS,而是这题的求解过程和A*算法思路类似。
- 设估价函数 F ( x ) = G ( x ) + H ( x ) F(x)=G(x)+H(x) F(x)=G(x)+H(x),
- G ( x ) G(x) G(x)是当前已经走了的距离, H ( x ) H(x) H(x)是 x x x到 n n n的实际最短路,
- 先建反向边跑一遍最短路求出所有的 H ( x ) H(x) H(x),然后从 1 1 1开始按A*的思路求解,
- 每次在队列里取出 F ( x ) F(x) F(x)最小的点,把它能连向的点加入队列,使其 F ( x ) = G ( x ) + H ( x ) F(x)=G(x)+H(x) F(x)=G(x)+H(x),
- 当第 K K K次取出终点 N N N时,当前的距离就是第 K K K短路。
- 但要注意,本题中相同路径长度算作相同的路径,所以还要记录上一次取出 N N N时的距离 l a s t last last,只有当前距离 G ( x ) > l a s t G(x)>last G(x)>last时才 c n t + 1 cnt+1 cnt+1。
- 还有一个优化,对于每个点都记录一个 c n t [ x ] cnt[x] cnt[x],如果 c n t [ x ] > K cnt[x]>K cnt[x]>K则不需要再取出点 x x x,因为到 x x x已经有了 K K K种不同的路径长度,总有一种会是到终点的第 K K K短路的一部分。
代码
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
#define N 10010
#define M 100010
struct
{
int x,y,l;
}a[M];
int last[N],nxt[M],to[M],ln[M],len=0;
int dis[N],q[N],vi[N],s=1,cnt[N],ls[N];
struct node
{
int x,s,t;
}f[M*20];
void add(int x,int y,int l)
{
to[++len]=y;
nxt[len]=last[x];
ln[len]=l;
last[x]=len;
}
void down(int k)
{
while((k*2<=s&&f[k*2].s<f[k].s)||(k*2<s&&f[k*2+1].s<f[k].s))
{
int l=k*2;
if(k*2<s&&f[k*2+1].s<f[k*2].s) l++;
swap(f[k],f[l]);
k=l;
}
}
void up(int k)
{
while(k>1&&f[k/2].s>f[k].s)
{
swap(f[k],f[k/2]);
k/=2;
}
}
int main()
{
int n,m,k,i;
scanf("%d%d%d",&n,&m,&k);
for(i=1;i<=m;i++)
{
scanf("%d%d%d",&a[i].x,&a[i].y,&a[i].l);
add(a[i].y,a[i].x,a[i].l);
char ch;
while(ch=getchar(),ch!=10);
}
memset(dis,127,sizeof(dis));
dis[n]=0,q[1]=n,vi[n]=1;
int l=0,r=1;
while(l!=r)
{
l=l%(n+5)+1;
int x=q[l];
for(i=last[x];i;i=nxt[i])
{
int y=to[i];
if(dis[x]+ln[i]<dis[y])
{
dis[y]=dis[x]+ln[i];
if(!vi[y])
{
vi[y]=1;
r=r%(n+5)+1;
q[r]=y;
}
}
}
vi[x]=0;
}
memset(last,0,sizeof(last));
len=0;
for(i=1;i<=m;i++) add(a[i].x,a[i].y,a[i].l);
f[1].s=dis[1],f[1].t=0,f[1].x=1;
while(1)
{
int x=f[1].x,t=f[1].t;
f[1]=f[s];
s--;
down(1);
if(t>ls[x]) cnt[x]++,ls[x]=t;
if(cnt[n]==k)
{
printf("%d",t);
break;
}
if(cnt[x]>k) continue;
for(i=last[x];i;i=nxt[i])
{
f[++s].x=to[i];
f[s].t=t+ln[i];
f[s].s=f[s].t+dis[to[i]];
up(s);
}
}
return 0;
}