Submit: 550 Solved: 125
[ Submit][ Status][ Web Board]
Description
Alice想要从城市A出发到城市B,由于Alice最近比较穷(不像集训队陈兴老师是个rich second),所以只能选择做火车从A到B。不过Alice很讨厌坐火车,火车上人比较多,比较拥挤,所以Alice有很严格的要求:火车的相邻两站间的最大距离尽可能的短,这样Alice就可以在停站的时候下车休息一下。当然Alice希望整个旅途比较短。
Input
有多组测试数据。
每组测试数据的第一行有两个整数N,M,A,B(N<=2000, M<=50000, N >=2, A,B<=N),其中N是城市的个数,M是城市间通火车的个数。
A,B是Alice起始的城市与目的地城市,城市的标号从1开始。
接下来的M行每行三个整数u,v,w表示从u到v和从v到u有一条铁路,距离为w, u,v<=N, w<=10000。
Output
对于每组测试数据输出满足Alice要求的从A到B的最短距离。
Sample Input
3 3 1 2
1 2 80
1 3 40
2 3 50
3 3 1 2
1 2 90
1 3 10
2 3 20
4 5 1 4
1 2 8
1 4 9
1 3 10
2 4 7
3 4 8
Sample Output
90
30
15
初看没思路,细看还是没思路,想想能不能用最小生成树做,后来发现如果从起点到几个点有相同的距离的话,用prime也不好算。其实题目的意思可以转化成:去掉较长的边,看能否通过其他较之短的边而达到。譬如从A到B有多条路,一条是直达的路,DA-B=35,一条是先到C,DA-C15,然后由C到B,距离是30.所以按题意应该是先到C,再到B。此时,如果有C到F,DC-F=20,然后由F到B,DF-B=25,那么他应该走这样的路线:A---C---F---B;
所以可以这么考虑:设边的最大长度为DMAX,然后,逐渐减小DMAX的长度,如果减小后用SPFA依旧能算出距离D!=无穷大,说明该减小的操作是正确的,然后再减小。。。但如果用SPFA算出距离D为无穷大,说明减小到该极限长度后,出发点和目的地无法到达。此时就应该扩大DMAX的长度.
如果用暴力的话,会超时,所以得用二分了,二分最大边和最小边。
如果是最小值最大的话,应该也是这种上路,逐渐真大Dmin,然后balabala.
#include <iostream>
#include <cstdio>
#include <cstring>
#include <queue>
#include <algorithm>
using namespace std;
const int maxm=100000;
const int inf=0x3f3f3f3f;
int next[maxm],inq[maxm],first[maxm],v[maxm],w[maxm],d[maxm],dis[maxm];
int n,a1,b1,m,i,s,ed,w1,tmp;
int e;
int l,r,mid;
int cmp(int x,int y)
{
return x>y;
}
void init()
{
e=0;
memset(first,-1,sizeof(first));
}
void add_edge(int a,int b,int c)
{
w[e]=c;v[e]=b;next[e]=first[a];first[a]=e++;
}
int spfa(int x,int y)
{
queue<int >q;
memset(d,0x3f,sizeof(d));
d[x]=0;inq[x]=1;
q.push(x);
while(!q.empty())
{
int u=q.front();
q.pop();
inq[u]=0;
for(i=first[u];i!=-1;i=next[i])
if(w[i]<=mid)
{
if(d[v[i]]>d[u]+w[i])
{
d[v[i]]=d[u]+w[i];
if(!inq[v[i]])
q.push(v[i]),inq[v[i]]=1;
}
}
}
return d[y];
}
//* 用二分的思想去递减
int main()
{
int ans1,ans2,ans;
while(cin>>n>>m>>a1>>b1)
{
memset(inq,0,sizeof(inq));
init();
l=1<<30;
r=-1;
for(i=1;i<=m;i++)
{
scanf("%d%d%d",&s,&ed,&w1);
add_edge(s,ed,w1);
add_edge(ed,s,w1);
l=min(l,w1);//选出最小的边
r=max(r,w1);//选出最大的边
}
//l=1<<30;r=-1;
mid=(l+r)>>1;
//用二分的关键思路在于:把一些长的边“舍去”,看看能否通过较短的几条边到达。
//如果能,说明该边的却可以舍去,如果不能,说明应该扩大边的最大长度,
//即在二分的时候,选择右半边。
while(l<=r)
{
tmp=spfa(a1,b1);
//cout<<"tmp"<<tmp<<endl;
if(tmp==0x3f3f3f3f)
l=mid+1;
else
r=mid-1,ans=tmp;
mid=(l+r)>>1;
}
cout<<ans<<endl;
}
}
/*用暴力看看能不能实现,结果会超时
int main()
{
int ans1,ans2,ans,j;
while(cin>>n>>m>>a1>>b1)
{
memset(inq,0,sizeof(inq));
init();
for(i=1;i<=m;i++)
{
scanf("%d%d%d",&s,&ed,&w1);
add_edge(s,ed,w1);
add_edge(ed,s,w1);
dis[i]=w1;
}
sort(dis+1,dis+m+1,cmp);
for(j=1;j<=m;j++)
{
//cout<<"dis"<<j<<"="<<dis[j]<<endl;
mid=dis[j];
tmp=spfa(a1,b1);
//cout<<tmp<<endl;
if(tmp==inf)
{
mid=dis[j-1];
tmp=spfa(a1,b1);
break;
}
}
cout<<tmp<<endl;
}
return 0;
}*/