NOIP2014 - 提高组D2T2 - 寻找道路

题目

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 &lt; n ≤ 10 0 &lt; n \le 10 0<n10 0 &lt; m ≤ 20 0 &lt; m \le 20 0<m20;
对于 60 % 60\% 60% 的数据, 0 &lt; n ≤ 100 0 &lt; n \le 100 0<n100 0 &lt; m ≤ 2000 0 &lt; m \le 2000 0<m2000;
对于 100 % 100\% 100% 的数据, 0 &lt; n ≤ 10000 0 &lt; n \le 10000 0<n10000, 0 &lt; m ≤ 200000 0 &lt; m \le 200000 0<m200000, 0 &lt; x , y , s , t ≤ n 0 &lt; x,y,s,t \le n 0<x,y,s,tn, x , s ≠ t x,s \ne t x,s̸=t

题解

思路

分析题目中要求的两个条件:

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

仔细思考之后不难理解,寻找最短路的时候,路径上的点,其拓展出去的点也需要与终点联通(这里“拓展”指仅拓展一次),如下图中的图:

图1

显然 1 − &gt; 5 1-&gt;5 1>5 的最短路为 1 − &gt; 2 − &gt; 5 1-&gt;2-&gt;5 1>2>5 ,但是由于 2 2 2 指向的点 6 6 6 没有与终点联通,因此最短路为 1 − &gt; 3 − &gt; 4 1-&gt;3-&gt;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数组对应为:

i123456
book111110

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(可能读者看的时候已经下去了)

总结,这道题告诉我们:

①有很多优化的方法,当然比赛的时候保证正确率即可,没必要为时间纠结(只要不超限)

②当图中所有边权都相等的时候,可以采用广度优先搜索求最短路。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值