【Usaco2014Open银组】双导航(gpsdual)
题目
【题目描述】
FJ 最近网购了一台小车。但是由于他的草率,在选择加装物品时偶然地点击了两次“Submit” ,结果最后他的小车装了两台GPS 导航系统!更糟的是,这两个系统对于FJ 要走的路线经常做出矛盾的判断。
FJ 居住地区的地图由N 个路口(2<=N <=10,000)和M 条有向边(1<=M<=50,000)组成。第i 条路连接路口Ai(1 <=Ai<=N)和Bi(1 <=Bi<=N)。对于同一对路口,可能有多条路连接它们;并且一条无向边会被表示成两条方向相反,连接路口相同的边。FJ 的房子位于第一路口,他的农场位于路口N。从他的房子出发,经过一系列有向边,是可以到达农场的。
两个GPS 系统基于如上面所描述的相同地图。然而,每条路它们存储的通过所需时间不相同。道路i 根据第一GPS 系统需要Pi 单位时间来通过,根据第二GPS 系统需要Qi 单位时间来通过(每个通过时间均是在1 到100,000 范围内的整数)。
FJ 想要从他的房子自驾车到农场。然而,当FJ 在一条某一个GPS 系统认为不属于最短路径内(从道路起始点X 到农场的最短路径)的道路(从路口X 到路口Y )上行驶时,这个GPS 系统会大声警告(如果FJ 选择的道路两个系统都不认可,那么两个系统都会警告FJ)。
请帮助FJ 找出,如果他恰当地选择他的路线,他能收到的警告最少是多少。
如果当FJ 沿一条道路走的时候两个GPS 都在警告,那么总警告数+2。
【输入】
第一行,整数N 和M。
接下来M 行,第i 行有四个整数Ai,Bi, Pi,Qi 描述第i 条道路。
【输出】
输出单独一行一个整数——FJ 选择从他家到达农场的最佳线路之后,路上收到的最小总警告数。
【样例输入】
5 7
3 4 7 1
1 3 2 20
1 4 17 18
4 5 25 3
1 2 10 1
3 5 4 14
2 4 6 5
【样例输出】
1
【样例解释】
输入详述:这里有5 个路口和7 条有向道路。第一条道路从路口3 到路口4 连接两个路口;第一GPS 认为这条路需要7 单位时间来通过,接着第二GPS 认为它只需1单位时间,诸如此类。
输出详述:如果FJ 按照路线1->2->4->5 走,那么第一GPS 会在1->2 这条路上发出警告(它认为1->3 这条路更好)。然而,对于路线剩下的2->4->5,两个GPS 都快乐地坐着FJ 的小车通过了,因为每个GPS 都以为这是2 到5 的最短路。
题解
这题主要讲了Farmer John把奶牛们打包送到肯德基后赚了一大笔钱,就网购小汽车。不料脑子瓦特了一下,订了两个GPS。店家黑心,没有告诉FJ这一消息,反而给了他两个版本不同的、声音响亮的GPS导航,这两个导航会在FJ不在走它们所认为的最短路时鸣叫,苦苦折磨着可怜的FJ。FJ就决定找一条去农场的最佳路径,使他被折磨的次数最少。
这题一眼扫过去,最短路算法!
没错,这题就是最短路,直觉没有坑害你。
于是一些人就立刻敲代码了,但是,怎么最短路呢?!
First,做这道题之前,你必须明白一件事——所谓的最短路是指到终点的最短路,当FJ从点 i 出发,走的边不是GPS认为的从点 i 出发去终点所要走的边时(即不在点 i 到终点的最短路上)就会发出警告。
所以要初始化从每一个点出发到终点要走那一条边(只会走一条)。
怎么弄呢?其实可以先跑一次SPFA,计算第一个GPS中每一个点到终点的距离(设第 i 个点的距离为\({dis1_{i}}\)),跑一次SPFA,计算第二个GPS中每一个点到终点的距离(设第 i 个点的距离为\({dis2_{i}}\))。根据SPFA算法的思路,可以推出,对于每一个导航,当点 i 和点 j 通过第 k 条边相连,点 j 到终点的距离被点 i 通过第 k 条边更新时,如果后来都没有任何点更新了点 i 和点 j 的最短路,那么\({dis_{i}+lenth_{k}=dis_{j}}\)(\(lenth_{k}\)表示第k条边的长度),由于对于每一个导航都是如此,我就用\(dis\)来代替\(dis1\)和\(dis2\)了。
如果点 i 离终点的最短距离被更新了,那么\(dis_{i}+lenth_{k}<dis_{j}\),如果点 j的被更新了,那么\(dis_{i}+lenth_{k}>dis_{j}\)。有人就要问了,如果i和j都被更新了怎么办?那样就必须再白一件事:如果点j到终点的最短路径中仍然是要经过点i的话,那么\(dis_{i}+lenth_{k}=dis_{j}\)(想一想,为什么);如果点j到终点的最短路径已经不用经过点i,那么\(dis_{i}+lenth_{k}<dis_{j}\)。
所以,再求出最短路径后,我们就可以枚举每一条边。设这条边的起点是u,终点是v,编号是i,当导航1在FJ经过这一条边时会发出警告,就一定会满足\(dis1_{i}+lenth_{k}>dis1_{j}\);当导航2在FJ经过这一条边时会发出警告,就一定会满足\(dis2_{i}+lenth_{k}>dis2_{j}\)
但是,这一切都搞定了,怎么求最少警告次数呢?
最短路!
我们可以视如果FJ经过一条边会被警告k次,那这一条边的权值就为k,这样弄完以后,再从起点出发跑一次SPFA就好了。
提示:由于本题要跑多次SPFA,建议建一个专门的函数来做SPFA。
代码
#include<cstdio>
#include<cstdlib>
using namespace std;
struct edge
{
int From,Lenth,To;
void swap()
{
int t=From;
From=To;
To=t;
}
}a[50010],b[50010],c[50010];
int n,m,dis[10010],date[51000],start[10010],end[10010];
bool exist[10010];
int cmp(const void *x,const void *y)
{
edge xx=*(edge*)x,yy=*(edge*)y;
if(xx.From!=yy.From) return xx.From-yy.From;
return xx.To-yy.To;
}
void spfa(int st)
{
int i,j,u,v,head=0,tail=1;
qsort(a+1,m,sizeof(edge),cmp);
for(i=1;i<=n;i++) dis[i]=2e9;
a[m+1].From=0;
for(i=1;i<=m+1;i++)
{
if(a[i].From!=a[i-1].From)
{
start[a[i].From]=i;
end[a[i-1].From]=i-1;
}
}
date[1]=st,dis[st]=0;
while(head<tail)
{
head++;
if(head==51000) head=1;
u=date[head];
exist[u]=0;
for(i=start[u];i<=end[u];i++)
{
v=a[i].To;
if(dis[u]+a[i].Lenth<dis[v])
{
dis[v]=dis[u]+a[i].Lenth;
if(!exist[v])
{
exist[v]=1;
tail++;
if(tail==51000) tail=1;
date[tail]=v;
}
}
}
}
}
int main()
{
freopen("gpsdual.in","r",stdin);
freopen("gpsdual.out","w",stdout);
int i,j,k,x,y,lena,lenb,t;
scanf("%d%d",&n,&m);
for(i=1;i<=m;i++)
{
scanf("%d%d%d%d",&x,&y,&lena,&lenb);
a[i]=(edge){y,lena,x};
b[i]=(edge){y,lenb,x};
c[i]=(edge){y,0,x};
}
qsort(c+1,m,sizeof(edge),cmp);
spfa(n);
for(i=1;i<=m;i++)//交换
{
if(dis[a[i].From]+a[i].Lenth>dis[a[i].To]) c[i].Lenth++;
a[i]=b[i];
}
spfa(n);
for(i=1;i<=m;i++)//交换
{
if(dis[a[i].From]+a[i].Lenth>dis[a[i].To]) c[i].Lenth++;
c[i].swap();
a[i]=c[i];
}
spfa(1);
printf("%d\n",dis[n]);
return 0;
}