[NOIP2014 提高组] 寻找道路 题解

题目描述

在有向图 G 中,每条边的长度均为 11,现给定起点和终点,请你在图中找一条从起点到终点的路径,该路径满足以下条件:

  1. 路径上的所有点的出边所指向的点都直接或间接与终点连通。
  2. 在满足条件 11 的情况下使路径最短。

注意:图 G 中可能存在重边和自环,题目保证终点没有出边。

请你输出符合条件的路径的长度。

输入格式

第一行有两个用一个空格隔开的整数 n 和 m,表示图有 n 个点和 m 条边。

接下来的 m 行每行 22 个整数 x,y,之间用一个空格隔开,表示有一条边从点 x 指向点 y。

最后一行有两个用一个空格隔开的整数 s,t,表示起点为 s,终点为 t。

输出格式

输出只有一行,包含一个整数,表示满足题目描述的最短路径的长度。如果这样的路径不存在,输出 −1。

输入输出样例

输入 #2

6 6
1 2
1 3
2 6
2 5  
4 5
3 4
1 5

输出 #2

3

样例 2 解释

如上图所示,满足条件的路径为 1→3→4→5。注意点 2 不能在答案路径中,因为点 2 连了一条边到点 6,而点 6 不与终点 5 连通。

写在前面:

很好的思路题,当您长时间思考而无解时再来看本篇题解,请务必独立认真思考。当然您也可以把本篇题解来与您的思路进行对比进行自我能力的提升。

步入正题:

通过思考,可以看出:

题目中的点可以分为三种:

① 自己指向的节点都可以到达终点。

② 自己可以到达终点的点。

③ 普通的点。

那么题目需要的就是:

由①组成的连接了起点和终点的最短路径。

思路:

显然③包含②,②包含①。

那么我们就先通过③求出所有的②,再通过②求出①。

最后再来一遍BFS就能求出最短路径了。

步骤:

1.读入数据,并建立正向和反向边。
2.从终点反向BFS,求出所有的②。
3.对每个点判断是否满足①。
4.从起点正向BFS,只经过①点,求出最短路径。

数组解释:

1.inroad 表示是否为 ①
2.can 表示是否为 ②
3.dis 表示距离起点的距离,计算长度用,
4.side 正向边
5.edis 反向边

总结:

对于这种类型图论题,如果答案仅与终点有关的话,那么建反向边跑反向图的思路是十分高效并且好理解的,代码实现也很容易。

仅有一个终点,而不知起点。那么显然任意路径都会将终点作为路径上的一个必然点。利用所有路径的这个通性,改变思路,转而走反向图,就能改为从 已知的起点 , 走到 未知的终点 了。

未知的起点,确定的终点。如果枚举起点跑到终点的话自然不优,所以我们可以将条件转换,从已经确定好的终点反向查找起点,复杂度就会下降很多。这就是我对这类题的经验。

代码:

80行,有点长。如果你大括号换行并且再压压行,60行能搞定。

#include<iostream>
#include<vector>
#include<cstdio>
#include<algorithm>
#include<queue>
using namespace std;
bool inroad[10010],can[10010];       //inroad 表示此点是否可以出现在路径上  can 表示此点是否可以到达终点 
int dis[10010];                      //dis 表示此点距离起点的距离 
vector<int>side[10010];              //正向建边 
vector<int>edis[10010];              //反向建边 
int main()
{
    int n,m,a,b,s,t;
    cin>>n>>m;
    for(int i=1;i<=m;i++)            //读入并正反向建边 
    {
        cin>>a>>b;
        side[a].push_back(b);
        edis[b].push_back(a);
    }
    cin>>s>>t;
    can[t]=1;                        //终点先设置为 1 
    queue<int>que; 
    que.push(t);
    while(!que.empty())              //从终点反向查找② 
    {
        int now=que.front();
        que.pop();
        for(int i=edis[now].size()-1;i>=0;i--)
        {
            int to=edis[now][i];
            if(!can[to])
            {
                que.push(to);   
                can[to]=1;
            }
        }
    }
    if(!can[s])         //起点无法到达终点就直接结束程序 
    {
        cout<<"-1";
        return 0;
    }
    for(int i=1;i<=n;i++)       //判断哪些点是①
    {
        if(can[i])
        {
            inroad[i]=1;
            for(int j=side[i].size()-1;j>=0;j--)    //遍历所有的边 
            {
                int to=side[i][j];
                if(!can[to])            //如果它出边所到达的点无法到达终点,这个点就不可以出现在路径上 
                {
                    inroad[i]=0;
                    break;
                }
            }
        }
    }
    if(!inroad[s])                        //这里需要特殊判定一下起点是否满足条件,感谢 @WestTree 的HACK        
    {
        cout<<"-1";
        return 0;
        }
    dis[s]=1;que.push(s);
    while(!que.empty())                 //从起点bfs,只经过①点,找出最短距离 
    {
        int now=que.front();
        que.pop();
        if(now==t)                  //到达终点,输出结果 
        {
            cout<<dis[t]-1;
            return 0;
        }
        for(int i=side[now].size()-1;i>=0;i--)
        {
            int to=side[now][i];
            if(inroad[to]&&!dis[to])
            {
                dis[to]=dis[now]+1;
                que.push(to);
            }
        }
    }
    cout<<"-1";                     //否则还是无法到达 
}

在百忙之中写一篇题解也比较辛苦,别忘了点个赞

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值