[NOIP2014真题]寻找道路

先给一个数据不水的提交址:http://uoj.ac/problem/19

题目背景
NOIP2014 提高组 Day2 试题。

题目描述
在有向图 G 中,每条边的长度均为 1,现给定起点和终点,请你在图中找一条从起点到终点的路径,该路径满足以下条件:
1.路径上的所有点的出边所指向的点都直接或间接与终点连通。
2.在满足条件 1 的情况下使路径最短。
注意:图 G 中可能存在重边和自环,题目保证终点没有出边。
请你输出符合条件的路径的长度。

输入格式
第一行有两个用一个空格隔开的整数 n 和 m,表示图有 n 个点和 m 条边。
接下来的 m 行每行 2 个整数 x、y,之间用一个空格隔开,表示有一条边从点 x 指向点y。
最后一行有两个用一个空格隔开的整数 s、t,表示起点为 s,终点为 t。

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

样例数据1
输入
3 2
1 2
2 1
1 3
输出
1
样例数据2
输入
6 6
1 2
1 3
2 6
2 5
4 5
3 4
1 5
输出
3

备注
【样例1说明】
这里写图片描述
如上图所示,箭头表示有向道路,圆点表示城市。起点 1 与终点 3 不连通,所以满足题目描述的路径不存在,故输出 -1。

【样例2说明】
[NOIP2014真题]寻找道路_第1张图片
如上图所示,满足条件的路径为 1->3->4->5。注意点 2 不能在答案路径中,因为点 2 连了一条边到点 6,而点 6 不与终点 5 连通。

【数据说明】
对于 30% 的数据, 0<n100<m20
对于 60% 的数据, 0<n1000<m2000
对于 100% 的数据, 0<n10,0000<m200,0000<xystnxt

分析: 这道题正着十分难做,考虑反着做。反向建图,从终点往起点走,走不到的地方便是正着走不直接或间接指向终点的点。给他们打上标记,并把与他们距离为1的点也打上标记。之后相当于删掉这些点和指向他们的边,构造新图走dijkstra(SPFA也可)。注意,要特判起点,因为在我的算法中是删掉指向不满足点的边,但是起点没有入边,所以被UOJhack数据卡了。

代码:

#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
using namespace std;

int getint()
{
    int f=1,sum=0;
    char ch;
    for(ch=getchar();(ch>'9'||ch<'0')&&ch!='-';ch=getchar());
    if(ch=='-')
    {
        f=-1;
        ch=getchar();
    }
    for(;ch>='0'&&ch<='9';ch=getchar())
        sum=(sum<<3)+(sum<<1)+ch-48;

    return sum*f;
}

int tot,x,y,n,m,s,t,dis[10010];
bool bj1[10010],bj2[10010],visit[10010],exist[200010];
int first1[10010],nxt1[200010],to1[200010],w[200010],first2[10010],nxt2[200010],to2[200010];
queue<int> que; 

void addedge(int x,int y)//正反同时建边
{
    tot++;
    nxt1[tot]=first1[x];
    first1[x]=tot;
    to1[tot]=y;
    w[tot]=1;
    nxt2[tot]=first2[y];
    first2[y]=tot;
    to2[tot]=x;
}

void dfs(int u)
{
    bj1[u]=0;

    for(int p=first2[u];p;p=nxt2[p])
    {
        int v=to2[p];
        if(!visit[v])
        {
            visit[v]=1;
            dfs(v);
        }
    }
}

void SPFA()
{
    dis[s]=0;
    que.push(s);
    visit[s]=1;
    while(!que.empty())
    {
        int u=que.front();
        que.pop();
        visit[u]=0;
        for(int p=first1[u];p;p=nxt1[p])
        {
            if(exist[p])
            {
                int v=to1[p];
                if(dis[v]>dis[u]+w[p])
                {
                    dis[v]=dis[u]+w[p];
                    if(!visit[v])
                    {
                        visit[v]=1;
                        que.push(v);
                    }
                }
            }
        }
    }
}

int main()
{
    freopen("road.in","r",stdin);
    freopen("road.out","w",stdout);

    n=getint();m=getint();
    for(int i=1;i<=m;++i)
    {
        x=getint();y=getint();
        addedge(x,y);
    }

    s=getint();t=getint();
    memset(bj1,1,sizeof(bj1));//把所有点都打标记,然后dfs删掉能走到的点的标记

    dfs(t);

    for(int i=1;i<=n;++i)//与有标记的点距离为1的点也打上标记
        if(bj1[i])
            for(int p=first2[i];p;p=nxt2[p])
            {
                int v=to2[p];
                bj2[v]=1;//为什么要用新标记呢?因为用bj1会导致把与这些新标记的点距离为1的点也打上标记,如此恶性循环,整个图全是标记了。
            }

    memset(exist,1,sizeof(exist));
    for(int i=1;i<=tot;++i)//删边
    {
        int v=to1[i];
        if(bj1[v]==1||bj2[v]==1)
            exist[i]=0;
    }

    if(bj1[s]==1||bj2[s]==1)//特判起点
    {
        cout<<-1<return 0;
    }

    memset(dis,127,sizeof(dis));
    memset(visit,0,sizeof(visit));
    SPFA();

    if(dis[t]>200000)//终点没更新,就是走不通
        cout<<-1<else
        cout<return 0;
}

本题结。

你可能感兴趣的:(NOIP2014)