题目
NOIP2014-提高组D2T2-寻找道路 |
---|
难度级别:A; 运行时间限制:1000ms; 运行空间限制:51200KB; 代码长度限制:2000000B |
试题描述 |
在有向图
G
G
G 中,每条边的长度均为
1
1
1 ,现给定起点和终点,请你在图中找一条从起点到终点的路径,该路径满足以下条件: 1.路径上的所有点的出边所指向的点都直接或间接与终点连通。 2.在满足条件 1 的情况下使路径最短。 注意:图 G G G 中可能存在重边和自环,题目保证终点没有出边。 请你输出符合条件的路径的长度。 |
输入 |
第一行有两个用一个空格隔开的整数
n
n
n 和
m
m
m ,表示图有
n
n
n 个点和
m
m
m 条边。 接下来的 m m m 行每行 2 2 2 个整数 x , y x,y x,y ,之间用一个空格隔开,表示有一条边从点 x x x 指向点 y y y 。 最后一行有两个用一个空格隔开的整数 s , t s,t s,t,表示起点为 s s s ,终点为 t t t。 |
输出 |
输出只有一行,包含一个整数,表示满足题目描述的最短路径的长度。如果这样的路径不存在,输出 − 1 -1 −1 。 |
输入示例 |
7 15 2 4 4 6 7 5 5 1 4 3 6 3 2 3 6 2 5 7 2 4 6 7 7 6 6 7 2 4 5 6 7 1 |
输出示例 |
2 |
数据范围 |
对于
30
%
30\%
30% 的数据,
0
<
n
≤
10
0 < n \le 10
0<n≤10,
0
<
m
≤
20
0 < m \le 20
0<m≤20; 对于 60 % 60\% 60% 的数据, 0 < n ≤ 100 0 < n \le 100 0<n≤100, 0 < m ≤ 2000 0 < m \le 2000 0<m≤2000; 对于 100 % 100\% 100% 的数据, 0 < n ≤ 10000 0 < n \le 10000 0<n≤10000, 0 < m ≤ 200000 0 < m \le 200000 0<m≤200000, 0 < x , y , s , t ≤ n 0 < x,y,s,t \le n 0<x,y,s,t≤n, x , s ≠ t x,s \ne t x,s̸=t |
题解
思路
分析题目中要求的两个条件:
①路径上的所有点的出边所指向的点都直接或间接与终点连通。
仔细思考之后不难理解,寻找最短路的时候,路径上的点,其拓展出去的点也需要与终点联通(这里“拓展”指仅拓展一次),如下图中的图:
显然 1 − > 5 1->5 1−>5 的最短路为 1 − > 2 − > 5 1->2->5 1−>2−>5 ,但是由于 2 2 2 指向的点 6 6 6 没有与终点联通,因此最短路为 1 − > 3 − > 4 1->3->4 1−>3−>4 。
怎么知道某一个点是否与终点联通呢?只需要在存图的时候,反向存边到另一个数组(注意不要存到一起变成无向图),然后从终点进行广度优先搜索,每经过一个点就将其book值记为 1 1 1 ,表示可以连接到终点。
②在满足条件 1 的情况下使路径最短。
很显然,我们需要计算一条最短路径,但是值得一说的是,题目中规定有向图 G G G 中的每条边长度均为 1 1 1 ,因此对于求最短路的算法,我们不光可以使用djikstra或者bellman-ford等,还可以采用广度优先搜索。
初步完成代码
我的方法是:存一个正向的和一个反向的图,在反向的图中,从终点搜索到起点(这和在正向的图中,从起点搜索到终点无区别),在搜索时,每遇到一个点,就枚举这个点在正向的图所有出边指向的点是否与终点联通(即book值是否为1)。
queue<int>q1;q1.push(t);//广搜找能连接终点的点
while(q1.size())
{
int x,w;
x=q1.front();
book[x]=1;
q1.pop();
for(int i=head[0][x];i;i=edge[0][i].next)//反向的图中寻找
{
if(!book[edge[0][i].ver])
{
q1.push(edge[0][i].ver);
}
}
}
queue<pair<int,int> >q;//定义队列
q.push(make_pair(t,0));//将终点入队,距离为0
while(q.size())
{
int x,w;
x=q.front().first;
w=q.front().second;
q.pop();
if(x==s)//如果找到了起点
{
printf("%d",w);//输出距离,此时距离一定最小
return 0;
}
bool flag;
for(int i=head[0][x];i;i=edge[0][i].next)//反向图中寻找
{
flag=true;
for(int j=head[1][edge[0][i].ver];j;j=edge[1][j].next)//正向图中枚举
{
if(book[edge[1][j].ver]==0)//指向的点不能连接到终点
{
flag=false;
}
}
if(flag)//如果满足条件1
{
q.push(make_pair(edge[0][i].ver,w+1));
}
}
}
至此,我们的代码已经可以AC,只要注意数组大小,不要RE即可。
然而,在博主学校的OJ(老爷机)上,还是TLE了,我一看,最优解榜首的,只有40+ms,这差距也太大了!这说明我们的程序还有优化的空间。
优化代码
观察自己的代码,发现在寻找最短路的时候,又在正向图中找了一遍,这说明我们即使有book数组,但book数组中的值并不能代表这个点在最终搜索时,是否满足条件1。因此我们需要对book数组进行处理,从而避免重复搜索。
我们想到之前的图,跑完一遍广搜,book数组对应为:
i | 1 | 2 | 3 | 4 | 5 | 6 |
---|---|---|---|---|---|---|
book | 1 | 1 | 1 | 1 | 1 | 0 |
而 2 2 2 连接了 6 6 6 , 2 2 2 也不能被选中。因此我们扫一下book中每一个点,如果该点book值为 0 0 0 (即不与终点联通),那么我们应当把连接到它的点book值也置为 0 0 0 ,也就是在反向的图中,把该点出边指向的点book值置为 0 0 0 。
for(int i=1;i<=n;i++)
{
if(book[i]==0)
{
for(int j=head[i];j;j=edge[j].next)
{
book[edge[j].ver]=0;
}
}
}
然而…
这样修改是妥妥的WA,为什么呢?当年我们分析的时候,说路径上的点,其拓展出去的点也需要与终点联通,但是这里“拓展”指仅拓展一次,而我们在循环中book数组却在不断更新,又考虑
i
i
i 是从
1
1
1 到
n
n
n “不回头”的,最后更新出来的book数组值就跟个四不像一样,谁也说不准这到底是更新了几次才置为
0
0
0 的(也就是这个点拓展了几次的点不能与终点联通的)。
但这并不代表我们没办法了,在这里只需要将原先的book数组复制一份,在循环中只需要判断book2的值就好了。
memcpy(book2,book,sizeof(book));//这句话将book数组的值复制给book2数组
for(int i=1;i<=n;i++)
{
if(book2[i]==0)
{
for(int j=head[i];j;j=edge[j].next)
{
book[edge[j].ver]=0;
}
}
}
这样在广搜找最短路的时候,我们只需要判断book值是否为1就好了,无需额外搜索。然后我们发现,已经不需要存正向的图了,程序中所有的搜索都是基于反向的图的。因此果断删去减少空间。
为了追求极致,我们再加上快读,快上加快。
完整代码
#include<bits/stdc++.h>
struct Edge
{
int next,ver;
}edge[200001];
using namespace std;
int head[200001],tot,INF=1<<30;
queue<pair<int,int> >q;
bool book[10001],book2[10001];
inline int read()
{
int t=0;char ch=getchar();
while(ch<'0'||ch>'9')ch=getchar();
while(ch>='0'&&ch<='9'){t=t*10+ch-'0';ch=getchar();}
return t;
}
int main()
{
int n,m,s,t,a,b,i,x,w,j;
n=read();m=read();
for(i=1;i<=m;i++)
{
a=read();b=read();
edge[++tot].ver=a;
edge[tot].next=head[b];
head[b]=tot;
}
s=read();t=read();
q.push(make_pair(t,0));
while(q.size())
{
x=q.front().first;q.pop();book[x]=1;
for(i=head[x];i;i=edge[i].next)
if(!book[edge[i].ver])
q.push(make_pair(edge[i].ver,0)),book[edge[i].ver]=1;
}
for(i=1;i<=n;i++)book2[i]=book[i];
for(i=1;i<=n;i++)
if(book2[i]==0)
for(j=head[i];j;j=edge[j].next)
book[edge[j].ver]=0;
q.push(make_pair(t,0));
while(q.size())
{
x=q.front().first;
w=q.front().second;
q.pop();
if(x==s)
{
printf("%d",w);
return 0;
}
for(i=head[x];i;i=edge[i].next)
if(book[edge[i].ver])
q.push(make_pair(edge[i].ver,w+1));
}
printf("-1");//无解输出-1
}
写在后面
有趣的是,经过博主和同学共同讨论后的一番优化,我们成功地登上了 loj 的最优解榜单top5(可能读者看的时候已经下去了)
总结,这道题告诉我们:
①有很多优化的方法,当然比赛的时候保证正确率即可,没必要为时间纠结(只要不超限)
②当图中所有边权都相等的时候,可以采用广度优先搜索求最短路。