[校内测试]Formula 1(BFS)

=== ===

这里写图片描述
这里写图片描述
这里写图片描述
这里写图片描述

=== ===

题解

这题虽然只用了BFS这一种看起来再普通不过的做法但是当真是个大神题啊= =ATP当时考试考这个题的时候只写了个60pts暴力= =好煞笔有没有

显然要保证油箱容量最小就要保证相邻两个加油站之间最长的那条路最短。比较暴力的思路是求多源最短路然后连 k2 条边,边权为加油站两两之间的距离,然后从小到大枚举边直到S和T连通。

可是不暴力的思路怎么办呢?仍然把图看做带权图,点为有加油站的点,边为加油站两两之间的最短距离。我们要考虑在 O(n) 的时间内找到方案使得连接S和T的最长边最短。于是我们把S和T和所有加油站都放到队列里然后一块开始BFS,那么因为BFS每次扩展一个单位长度,那么长度不同的边会在不同的时间被连通,长度短的边会比长度长的边先连通。那么当发现S和T连通的时候当前所在的那条边就是答案。首先它是当前选择的连接S和T的路径上的最长边,因为它在所有边中最后一个被连通;其次它又是所有连通S和T的方案中最小的那条最长边,因为它是按照长度从小到大连通每条边的。

那,直接把所有点扔进去做BFS就可以了吗?实际上是不行的,因为如果直接这么做的话,BFS的扩展顺序是会影响结果的。尤其是当某两条边的边长差距只为1的时候。比如现在有一个图,是一条1,2,3,4四个点串起来的一条链(ATP比较懒就不画图了),其中1,3,4三个点有加油站,1为起点4为终点。如果按照我们一开始那么做的话,1,3,4这三个点先入队,然后先扩展1,1和2连通了;接下来扩展3,这就出了问题。如果它先扩展3->4的话当然再好不过,但它如果偏偏要扩展3->2的话,1和3这两个点就被联通了,它就会以为1和3之间的距离还没有3和4之间的距离大,接下来如果通过3和4联通了S和T,它就会输出1,这就很尴尬了。。。

那怎么办呢?我们可以发现出现这种问题的关键是两条边的差距太小。那么我们拉大边与边之间的差距就可以了!这里拉大差距的方法就是把原图中每两个点之间都加入一个新点,这样就把每条边的长度扩大了两倍。那么本来长度为1和2的两条边现在变成了长度为2和4的两条边,不管什么顺序扩展,长度不同的两条边都一定会按顺序相继连通,就没有上面提到的那个问题了!

代码

#include<cstdio>
#include<cstring>
#include<algorithm>
#define inc(x)(x=(x%100000)+1)
using namespace std;
int Time,n,m,k,S,T,tot,p[1000010],a[600010],next[600010],q[1000010],head,tail;
int father[1000100],dis[1000010],cnt;
bool ext[1000010],flag;
void add(int x,int y){
    tot++;a[tot]=y;next[tot]=p[x];p[x]=tot;
}
int find(int x){
    if (x==father[x]) return x;
    father[x]=find(father[x]);
    return father[x];
}
int main()
{
    freopen("secure.in","r",stdin);
    freopen("secure.out","w",stdout);
    scanf("%d",&Time);
    for (int wer=1;wer<=Time;wer++){
        memset(p,0,sizeof(p));
        memset(ext,false,sizeof(ext));
        memset(dis,-1,sizeof(dis));
        scanf("%d%d%d",&n,&m,&k);
        tot=0;cnt=n;flag=false;
        for (int i=1;i<=k;i++){
            scanf("%d",&q[i]);
            ext[q[i]]=true;dis[q[i]]=0;
        }
        for (int i=1;i<=m;i++){
            int x,y;++cnt;
            scanf("%d%d",&x,&y);
            add(x,cnt);add(cnt,x);
            add(cnt,y);add(y,cnt);
        }//在每条边上都新增一个点
        for (int i=1;i<=cnt;i++) father[i]=i;
        scanf("%d%d",&S,&T);
        head=0;tail=k;
        if (ext[T]==false){
            inc(tail);q[tail]=T;
            dis[T]=0;ext[T]=true;
        }
        while (head!=tail){
            int u,r1,r2;
            inc(head);u=q[head];
            r1=find(u);
            for (int i=p[u];i!=0;i=next[i]){
                r2=find(a[i]);
                if (r1!=r2) father[r2]=r1;
                if (ext[a[i]]==false){
                    dis[a[i]]=dis[u]+1;
                    inc(tail);q[tail]=a[i];
                    ext[a[i]]=true;
                }
                if (find(S)==find(T)){
                    printf("%d\n",dis[u]+1);
                    flag=true;break;
                }//如果S和T联通了就直接输出答案
            }
            if (flag==true) break;
        }
        if (find(S)!=find(T)) printf("-1\n");
    }
    return 0;
}

偏偏在最后出现的补充说明

这道题十分巧妙的利用了BFS按路径长度扩展的顺序,并且利用把边与边之间的差距扩大这种方式来避免BFS中顺序无法控制造成的错误。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值