【Codevs】3731 寻找道路 --2014年NOIP全国联赛提高组

–>题面

题目描述 Description

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

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

2.在满足条件1的情况下使路径最短。

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

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

输入描述1 Input Description

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

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

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

输出描述1 Output Description

输出文件名为road.out。

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

样例输入1 Sample Input

3 2

1 2

2 1

1 3

样例一

样例输出1 Sample Output

-1

样例输入2 Sample Input

6 6

1 2

1 3

2 6

2 5

4 5

3 4

1 5

样例二

样例输出1 Sample Output

3

数据范围及提示 Data Size & Hint

对于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。

–>分析

有2个条件嗯……第二个没啥意思 所以主要是满足第一个条件
与终点相连的点就是能走到终点的点;
也就是说 把有向边反向之后 能从终点走到的点;
所以我们再建一个反向图;
然后我们把这些能从终点走到的点标记一下;
这样那些在正向图中走不到终点的点就不会被标记;
还有一些点我们也不能走,比如样例 2 中的 2 点,因为它连了一个 被我们否点的 6 点;
所以我们在spfa 的时候 判断一下 当前的点 是否连了不该连的点,如果连了 我们不用;
这样跑出最短路来就不会有不满足条件的点了。

#include <iostream>
#include <cstdio>
#include <queue>
#include <cstring>
using namespace std;

const int MAX_V = 10000 + 15;
const int MAX_E = 200000 + 15;
int V, E, Ts, Te, tot = 0, tot2 = 0;
int d[MAX_V], first[MAX_V], nxt[MAX_E << 1];
int first2[MAX_V], nxt2[MAX_E << 1];//反建图 
bool spfaused[MAX_V],could[MAX_V],t[MAX_V];
struct edge{
    int from, to, cost;
};
edge es[MAX_E << 1];
edge es2[MAX_E << 1];//反建图 

void init()
{
    memset(first,-1,sizeof(first));
    memset(first2,-1,sizeof(first2));
}

void build(int ff, int tt, int dd)
{
    es[++tot] = (edge){ff,tt,dd};
    nxt[tot] = first[ff];
    first[ff] = tot;
}

void build2(int ff, int tt, int dd)//反建图 
{
    es2[++ tot2] = (edge){ff,tt,dd};
    nxt2[tot2] = first2[ff];
    first2[ff] = tot2;
}

void findcan(int s)//寻找能到达的点并标记 
{
    could[s] = 1;
    t[s] = 1;
    for(int i = first2[s]; i != -1; i = nxt2[i])
    {
        int v = es2[i].to;
        if(!t[v])
            findcan(v);
    }
}

bool cankill(int x)//是否能够被杀(不能用) 
{
    for(int i = first[x]; i != -1; i = nxt[i])
    {
        int v = es[i].to;
        if(!could[v])
            return true;
    }
    return false;
}

queue<int> q;
void spfa(int s)
{
    memset(d,0x3f,sizeof(d));
    d[s] = 0;
    q.push(s);
    spfaused[s] = 1;
    while(!q.empty())
    {
        int x = q.front();
        q.pop();
        spfaused[x] = 0;
        if(cankill(x))//看一下这个点是不是连了不该连的点 
            continue;
        for(int i = first[x]; i != -1; i = nxt[i])
        {
            int v = es[i].to;
            /*
            下面可以改为 :
            if(!used[v])
            {
                d[v] = d[x] + 1;
                q.push(v);
                used[v] = 1;
            } 
            */ 
            if(d[v] > d[x] + es[i].cost)
            {
                d[v] = d[x] + es[i].cost;
                if(!spfaused[v])
                {
                    q.push(v);
                    spfaused[v] = 1;
                }
            }
        }
    }
}
int main()
{
    init();
    scanf("%d%d",&V, &E);
    for(int i = 1; i <= E; i ++)
    {
        int x, y;
        scanf("%d%d",&x,&y);
        build(x,y,1);
        build2(y,x,1);
    }
    scanf("%d%d",&Ts, &Te);
    findcan(Te);
    spfa(Ts);
    if(d[Te] < 0x3f3f3f3f - 233)
        printf("%d\n",d[Te]);
    else printf("-1\n");
    for(int i = 1; i <= V; i ++)
    return 0;
}

K.O.

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值