洛谷P2296 寻找道路

本文介绍如何使用SPFA算法在有向图中寻找满足特定条件的最短路径,条件为路径上所有点的出边指向的点需直接或间接与终点连通。通过构建反向图预处理节点连通性,实现高效路径搜索。

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

链接:P2296

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

1 路径上的所有点的出边所指向的点都直接或间接与终点连通。
2 在满足条件 1的情况下使路径最短。
注意:图 GG 中可能存在重边和自环,题目保证终点没有出边。

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

输入格式
第一行有两个用一个空格隔开的整数 nn 和 mm,表示图有 nn 个点和 mm 条边。

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

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

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

输入输出样例
输入 #1
3 2
1 2
2 1
1 3
输出 #1
-1
输入 #2
6 6
1 2
1 3
2 6
2 5
4 5
3 4
1 5
输出 #2
3

数据范围

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

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

对于100%100%的数据,0 < n \le 10000, 0 < m \le 200000,0 < x,y,s,t \le n, x,s \ne t0<n≤10000,0<m≤200000,0<x,y,s,t≤n,x,s≠t。


我们要求最短路,但是加了一个前提条件:

路径上的所有点的出边所指向的点都直接或间接与终点连通。

那么我们就需要预处理一下,由于又是判断两个节点是否连通的问题。
总结判断两个节点是否联通的方法:
1.使用并查集 (适合无向图处理)
2.使用最短路
3.采用BFS和DFS预处理
由于本题要求的是有向图,那么我们就可以采用SPFA建立一个反向图来判断终点到各个点是否连通,这样就可以省下一点时间了。然后我们在第二个SPFA求起点到终点的最短路时判断一下当前与这个点相连的点是否与终点相连即可。

代码:

#include <cstdio>
#include <iostream>
using namespace std;
struct CZP
{
    int next,to,dis;
}a[200001],b1[200001];
int que[20000001],b[100001],dis[100001],h[100001],h1[100001],dis1[100001];
int top,top1,m,n,sx,ex; 
void cun(int from,int to)
{
    a[++top].next=h[from];
    a[top].to=to;
    a[top].dis=1;
    h[from]=top;
}   //邻接表存储
void cun1(int from,int to)
{
    b1[++top1].next=h1[from];
    b1[top1].to=to;
    b1[top1].dis=1;
    h1[from]=top1;
}   //建立一个反向图用于判断各点与终点是否连通
int pd(int x)
{
    int k=h[x];
    while (k!=-1)
    {
        if (dis1[a[k].to]>=1e9)
        return 0;
        k=a[k].next;
    }  //判断与其相连的点是否与终点相连,即反向图的最短路是否是初始化的最大值
    return 1;  
}
int main()
{
    scanf("%d%d",&n,&m);
    for (int i=1;i<=n;i++)
    h1[i]=-1,h[i]=-1;
    for (int i=1;i<=m;i++)
    {
        int x,y;
        scanf("%d%d",&x,&y);
        cun(x,y);
        cun1(y,x);
    }
    scanf("%d%d",&sx,&ex);
    int head=0,tail=1;
    for (int i=1;i<=n;i++)
    dis1[i]=1e9;
    dis1[ex]=0;
    b[ex]=1;
    que[1]=ex;
    do
    {
        head++;
        int v=que[head];
        int k=h1[v];
        b[v]=0; 
        while (k!=-1)
        {
            if (dis1[b1[k].to]>dis1[v]+b1[k].dis)
            {
            dis1[b1[k].to]=dis1[v]+b1[k].dis;
              if (!b[b1[k].to])
              {
                tail++;
                que[tail]=b1[k].to;
                b[b1[k].to]=1;
              }
            }
            k=b1[k].next;
    }
        }while (head<tail);  //第一个SPFA用于判断各点是否与终点相连
    head=0,tail=1;
    for (int i=1;i<=n;i++)
    dis[i]=1e9,b[i]=0;
    dis[sx]=0;
    b[sx]=1;
    que[1]=sx;
    do
    {
        head++;
        int v=que[head];
        int k=h[v];
        b[v]=0; 
        while (k!=-1)
        {
            if (dis[a[k].to]>dis[v]+a[k].dis && pd(a[k].to))  //如果由相连的点与终点不相连则不更新起点到这个点的最短距离并且不入队列
            {
            dis[a[k].to]=dis[v]+a[k].dis;
              if (!b[a[k].to])
              {
                tail++;
                que[tail]=a[k].to;
                b[a[k].to]=1;
              }
            }
            k=a[k].next;
    }
        }while (head<tail);
        if (dis[ex]>=1e9)  //特判: 如果起点与终点不连通
        printf("-1");
        else
        printf("%d",dis[ex]);
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值