NOIP2014提高组 寻找道路

本文介绍了解决特定有向图中寻找从起点到终点的最短路径问题的方法。该问题要求路径上的所有点都能直接或间接与终点连通。通过构建反向图并进行两次宽度优先搜索实现解决方案。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

描述

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

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

注意:图 G 中可能存在重边和自环,题目保证终点没有出边。 请你输出符合条件的路径的长度。

格式

输入格式

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

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

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

输出格式

输出只有一行,包含一个整数,表示满足题目描述的最短路径的长度。

如果这样的路径不存在,输出-1。

样例1

样例输入1[复制]

3 2
1 2
2 1
1 3

样例输出1[复制]

-1

样例2

样例输入2[复制]

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

样例输出2[复制]

3

限制

对于 30%的数据,0 < n ≤ 10,0 < m ≤ 20;

对于 60%的数据,0 < n ≤ 100,0 < m ≤ 2000;

对于 100%的数据,0 < n ≤ 10,000,0 < m ≤ 200,000,0 < x,y,s,t ≤ n,x ≠ t。

提示

【输入输出样例1说明】

图片

如上图所示,箭头表示有向道路,圆点表示城市。起点 1 与终点 3 不连通,所以满足题目描述的路径不存在,故输出-1。

【输入输出样例2说明】

图片

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


 

解题思路:这个程序感觉是一个宽搜,程序很冒险,因为内存比较大,可以说是恰好过关,但这个程序方法也是

大家普遍能想到的,只不过稍稍改变了一下,就让原本不可能过去的程序过关了。

很巧妙地设计了一个反向的图,两次反着搜,就有结果了。

可能比较绕,因为有的【】里又有好多【】,所以一定要明白每个字母表示什么。

和最优贸易有一点像,如果比成两张地图的话,第一次搜索就是找到能到终点的点,

第二次就是找到最短的路,再将两张地图一重叠,找到最小值,就是答案了。

 

 

 

程序:

program heyingjin;

var m,n,x,y,i,j,s,t,l,r,head,tail:longint;

   a:array[1..10000,0..3000]of longint;//        记录到达i,的第j个点,存到a[i,j];i的点的总数存入ai0】中

                                          这里存储的比较特别,因为我之前都是i表示起点的,总数存入另一个数组, 

                                          它这里既节省了空间,又确保图可以反着来搜;   

   team,d:array[1..100000]of longint; //team就是建的队,d之后是要记录哪些点是能到终点的,并且到这一个点的步数

    v:array[1..10000]of boolean;        //也是记录删掉题上说明的不需要的点后的点,记为true

begin

 readln(n,m);

  fori:=1 to m do

   begin

   read(x,y);

   inc(a[y,0]);a[y,a[y,0]]:=x;        //之前已经说过,不解释了。。。

   end;

 readln(l,r);

 fillchar(v,sizeof(v),true);         //初始化

 team[1]:=r;                          //反着搜,所以队头设为终点

  fori:=1 to n do d[i]:=1000000000;   

 d[r]:=0;                             //队头得点赋值0,表示从队头开始往后处理了

 head:=0;

 tail:=1;

 while head<tail do                  // 维护队伍

   begin

     inc(head);

     for i:=1 to a[team[head],0] do  //(以下开始高能烧脑模式,清楚了什么表示什么再来看)a[team[head],0]表示能到队头的所有点的数量;

        ifd[team[head]]+1<=d[a[team[head],i]] then  //把那些能到队头的点的值变小,之前赋值的100000000

         begin

           inc(tail);                              

           team[tail]:=a[team[head],i];         //能到队头的点进队  

           d[a[team[head],i]]:=d[team[head]]+1; //赋值(也就是到这一个点步数)

         end;

   end;

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~以上就找到了能到队头的点,这些点的di】值都小于100000000~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

  fori:=1 to n do

    ifd[i]>=1000000000 then     //      这一堆表示那些直接连接着到不了终点的点都要消失,也就是false

      begin                      //

       v[i]:=false;             //

       for j:=1 to a[i,0] do    //

       v[a[i,j]]:=false;        //

     end;                       //

 v[l]:=true;

 team[1]:=r;                         //再像上面一样,再搜一遍,还是从终点搜到起点,所以队头就还是终点

  fori:=1 to n do d[i]:=1000000000;   //再次初始化

 d[r]:=0;

 head:=0;

 tail:=1;

 while head<tail do              //维护队伍

   begin

     inc(head);

     for i:=1 to a[team[head],0] do 

        if(d[team[head]]+1<=d[a[team[head],i]]) and v[a[team[head],i]] then  //表示在裁员以后还能到队头的点

         begin

           inc(tail);

           team[tail]:=a[team[head],i];

           d[a[team[head],i]]:=d[team[head]]+1;        //d这里就表示到达这个新队尾的步数

         end;

 end;

  ifd[l]=1000000000 then writeln(-1)       //这步其实可以写在第一次搜索完后面的,但写在这里感觉条理更清晰(但对于答案为-1的题,更耗时间);

   else writeln(d[l]);      //输出,结束

end.

 

 

 

 对两组数据进行分析一下:

 

 

 

 

 

心得:很多时候我都好奇为什么我总做不出程序,原来只是一个细节的差别,我们需要的是一种思维,一种faith。

 


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值