先给一个数据不水的提交址: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说明】
如上图所示,满足条件的路径为 1->3->4->5。注意点 2 不能在答案路径中,因为点 2 连了一条边到点 6,而点 6 不与终点 5 连通。
【数据说明】
对于 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的点也打上标记。之后相当于删掉这些点和指向他们的边,构造新图走dijkstra(SPFA也可)。注意,要特判起点,因为在我的算法中是删掉指向不满足点的边,但是起点没有入边,所以被UOJhack数据卡了。
代码:
#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<string>
#include<ctime>
#include<cmath>
#include<algorithm>
#include<cctype>
#include<iomanip>
#include<queue>
#include<set>
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<<endl;
return 0;
}
memset(dis,127,sizeof(dis));
memset(visit,0,sizeof(visit));
SPFA();
if(dis[t]>200000)//终点没更新,就是走不通
cout<<-1<<endl;
else
cout<<dis[t]<<endl;
return 0;
}
本题结。