周赛1003题解 hdu 1598 find the most comfortable road (二分+bfs 或者 并查集)

find the most comfortable road

Time Limit: 1000/1000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others)
Total Submission(s): 3145    Accepted Submission(s): 1334


Problem Description
XX星有许多城市,城市之间通过一种奇怪的高速公路SARS(Super Air Roam Structure---超级空中漫游结构)进行交流,每条SARS都对行驶在上面的Flycar限制了固定的Speed,同时XX星人对 Flycar的“舒适度”有特殊要求,即乘坐过程中最高速度与最低速度的差越小乘坐越舒服 ,(理解为SARS的限速要求,flycar必须瞬间提速/降速,痛苦呀 ),
但XX星人对时间却没那么多要求。要你找出一条城市间的最舒适的路径。(SARS是双向的)。
 

Input
输入包括多个测试实例,每个实例包括:
第一行有2个正整数n (1<n<=200)和m (m<=1000),表示有N个城市和M条SARS。
接下来的行是三个正整数StartCity,EndCity,speed,表示从表面上看StartCity到EndCity,限速为speedSARS。speed<=1000000
然后是一个正整数Q(Q<11),表示寻路的个数。
接下来Q行每行有2个正整数Start,End, 表示寻路的起终点。
 

Output
每个寻路要求打印一行,仅输出一个非负整数表示最佳路线的舒适度最高速与最低速的差。如果起点和终点不能到达,那么输出-1。
 

Sample Input
  
  
4 4 1 2 2 2 3 4 1 4 1 3 4 2 2 1 3 1 2
 

Sample Output
  
  
1 0
 

Author
ailyanlu
 

Source


题意:
从起点s到终点e找到一条路径,使这条路径中的最大边的权值-最小边的权值最小。
介绍两种方法:1、二分+bfs   2.并查集

思路1:(二分+bfs)
如果采用纯暴力的方式的话,枚举最大边、枚举最小边、bfs判断s能否到e,那么复杂度是q*m*m*n,(q为询问个数)太高了,不能过,于是想到枚举最小边,二分出在该最小边的情况下,能满足条件的最小的最大边,然后bfs判断s能否到e,复杂度就为q*m*log(m)*n,这个复杂度就能接受了。

ps1:bfs判断s能否到e,涉及到邻接表建图以及简单bfs搜索,大一的可以自己学一下,在以后的讲座中也会给大家讲解的。

代码:
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <cmath>
#include <string>
#include <map>
#include <stack>
#include <vector>
#include <set>
#include <queue>
#pragma comment (linker,"/STACK:102400000,102400000")
#define maxn 205
#define MAXN 2005
#define mod 1000000009
#define INF 0x3f3f3f3f
#define pi acos(-1.0)
#define eps 1e-6
typedef long long ll;
using namespace std;

int n,m,ans,cnt,tot,flag;
int a[2005],pp[205],q[205];  // pp-相当于邻接点的头指针  q-模拟队列
bool vis[205];  // 标记数组
int s,e,mi,ma,le,ri,mid;
struct Node
{
    int v,w,next;
} edge[MAXN];  // 边

void addedge(int u,int v,int w) // 建图
{
    cnt++;
    edge[cnt].v=v;
    edge[cnt].w=w;
    edge[cnt].next=pp[u];
    pp[u]=cnt;
}
bool bfs1()
{
    int i,j,t,u,v,head=0,tail=-1;
    memset(vis,0,sizeof(vis));  // 标记数组清空
    vis[s]=1; 
    q[++tail]=s; // 起点进队列
    while(head<=tail) // 当队中有元素
    {
        u=q[head]; // 出队首
        if(u==e) return true ; // 为e则能到达
        head++;
        for(i=pp[u];i;i=edge[i].next) // 访问u的邻接点
        {
            v=edge[i].v;
            if(!vis[v]&&edge[i].w>=mi)
            {
                vis[v]=1;
                if(v==e) return true ; // 为e则能到达
                q[++tail]=v;
            }
        }
    }
    return false ;
}
bool bfs2() // 和bfs1基本一样
{
    int i,j,t,u,v,head=0,tail=-1;
    memset(vis,0,sizeof(vis));
    vis[s]=1;
    q[++tail]=s;
    while(head<=tail)
    {
        u=q[head];
        if(u==e) return true ;
        head++;
        for(i=pp[u];i;i=edge[i].next)
        {
            v=edge[i].v;
            if(!vis[v]&&edge[i].w>=mi&&edge[i].w<=a[mid])
            {
                vis[v]=1;
                if(v==e) return true ;
                q[++tail]=v;
            }
        }
    }
    return false ;
}
void solve()
{
    int i,j,t,q;
    scanf("%d",&q);
    sort(a+1,a+m+1); // 将边按照权值排序
    while(q--)
    {
        scanf("%d%d",&s,&e);
        ans=INF;        // 将答案出初始化为无穷大
        for(i=1; i<=m; i++) // 枚举最小边
        {
            mi=a[i];
            if(!bfs1()) break ;  // 看只走边>=最小边时s能否到达e 不能则后面的都不能到达了 退出循环
            le=i;
            ri=m;
            while(le<ri)  // 二分最大值所在的数组下标
            {
                mid=(le+ri)>>1;
                if(bfs2()) ri=mid;  // 看只走边在[a[le],a[ri]]范围内 s能否到达e
                else le=mid+1;
            }
            ans=min(ans,a[le]-mi); // 更新答案
        }
        if(ans==INF) ans=-1; 
        printf("%d\n",ans);
    }
}
int main()
{
    int i,j,t;
    while(~scanf("%d%d",&n,&m))
    {
        int u,v,w;
        cnt=0;
        memset(pp,0,sizeof(pp));
        for(i=1; i<=m; i++)
        {
            scanf("%d%d%d",&u,&v,&w);
            addedge(u,v,w);  // 建图
            addedge(v,u,w);
            a[i]=w; // 记录权值
        }
        solve();
    }
    return 0;
}

思路2:(并查集)
将边按照权值排序,枚举最小边,然后枚举最大边,用并查集判断s是否能到e了(如果在同一集合就能到了)。

ps:不懂并查集的可以自己学一下,在以后的讲座中也会给大家讲解的。

代码:
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <cmath>
#include <string>
#include <map>
#include <stack>
#include <vector>
#include <set>
#include <queue>
#pragma comment (linker,"/STACK:102400000,102400000")
#define maxn 1005
#define MAXN 2005
#define mod 1000000009
#define INF 0x3f3f3f3f
#define pi acos(-1.0)
#define eps 1e-6
typedef long long ll;
using namespace std;

int n,m,ans,cnt,tot,flag;
struct Node
{
    int u,v,w;
}edge[maxn];
int s,e;
int pre[205];

bool cmp(Node xx,Node yy) // 排序用的比较函数
{
    return xx.w<yy.w;
}
int Find(int x) // 递归找到根节点
{
    if(x==pre[x]) return x;
    pre[x]=Find(pre[x]);
    return pre[x];
}
void Merge(int x,int y) // 合并集合
{
    int u=Find(x),v=Find(y);
    if(u!=v) pre[u]=v;
}
void solve()
{
    int i,j,t,q;
    scanf("%d",&q);
    sort(edge+1,edge+m+1,cmp); // 按照边权值排序
    while(q--)
    {
        scanf("%d%d",&s,&e);
        ans=INF;
        for(i=1; i<=m; i++) // 枚举最小边
        {
            for(j=1;j<=n;j++) pre[j]=j;  // 并查集初始化
            for(j=i;j<=m;j++)  // 枚举最大边
            {
                Merge(edge[j].u,edge[j].v);
                if(Find(s)==Find(e)) break ;
            }
            if(j<=m) // 能到达则更新ans
            {
                ans=min(ans,edge[j].w-edge[i].w);
            }
        }
        if(ans==INF) ans=-1;
        printf("%d\n",ans);
    }
}
int main()
{
    int i,j,t;
    while(~scanf("%d%d",&n,&m))
    {
        for(i=1; i<=m; i++)
        {
            scanf("%d%d%d",&edge[i].u,&edge[i].v,&edge[i].w);
        }
        solve();
    }
    return 0;
}






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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值