P3371 【模板】单源最短路径(弱化版) 题解

博客园同步

原题链接

简要题意:

给定一张有向图,求从源点开始,向各点的最短路(无负权)。(所谓的“单源最短路径”)

显然,如果你第一次见这种最短路的模板,你可能会用 记忆化搜索 来解决。

但是很遗憾,记忆化搜索的 “记忆化” 在图中很难得到有效体现;还是会稳稳的 TLE \texttt{TLE} TLE.

为了验证记忆化搜索的错误性,本人分析一下。

对于当前点向外扩展,如果已有答案比现有要优,则更新答案。

时间复杂度: O ( m 2 ) O(m^2) O(m2).(这个时间很玄学)

实际得分: 40 p t s 40pts 40pts.

#pragma GCC optimize(2)
#include
using namespace std;

const int N=2e5+1;

inline int read(){char ch=getchar();int f=1;while(ch<'0' || ch>'9') {if(ch=='-') f=-f; ch=getchar();}
	int x=0;while(ch>='0' && ch<='9') x=(x<<3)+(x<<1)+ch-'0',ch=getchar();return x*f;}

int n,m,f[N]; //f[i] 是从源点开始的最短路答案
vector<pair<int,int> >G[N];

inline void dfs(int dep,int bs) {
	if(f[dep]!=-1 && f[dep]<=bs) return; //有答案且不优
	f[dep]=bs; //记录
	for(int i=0;i<G[dep].size();i++)
		dfs(G[dep][i].first,G[dep][i].second+bs); //走
}

int main(){
	n=read(),m=read();
	int s=read();
	for(int i=1;i<=m;i++) {
		int x=read(),y=read(),z=read();
		G[x].push_back(make_pair(y,z));
	} memset(f,-1,sizeof(f));
	dfs(s,0); for(int i=1;i<=n;i++) printf("%d ",f[i]==-1?INT_MAX:f[i]);
	return 0;
}

当然如果你会 Floyd 这题可以得到 70 分。。

但是,如果我们完全不了解最短路,也可以通过搜索解决本题。

既然, dfs \texttt{dfs} dfs 已经死了,那么是否可以考虑 bfs \texttt{bfs} bfs

是可以试试看的。比方说,从源点开始入队扩展,然后对当前点 不在队列里 的点更新答案(如果在队列里就不用更新,因为队列里的永远最优),然后出队。

那么你会说了,如果一个点入队多次,会不会有问题呢?

不会。

因为,只要它不在队列里,别的点就会更新它;很可能它刚刚更新了一个点出队,然后那个点反过来再更新它一遍,这是完全可能出现的,没有问题。

时间复杂度: O ( n 2 ) O(n^2) O(n2).(具体详见下文分析)

实际得分: 100 p t s 100pts 100pts.

#pragma GCC optimize(2)
#include
using namespace std;

const int N=2e5+1;

inline int read(){char ch=getchar();int f=1;while(ch<'0' || ch>'9') {if(ch=='-') f=-f; ch=getchar();}
	int x=0;while(ch>='0' && ch<='9') x=(x<<3)+(x<<1)+ch-'0',ch=getchar();return x*f;}

int n,m,s,dis[N]; //dis[i] 存储答案
vector<pair<int,int> >G[N];
bool vis[N]; //vis[i] 表示是否在队中

inline void SPFA() {
	queue<int>q; memset(vis,0,sizeof(vis));
	for(int i=1;i<=n;i++) dis[i]=INT_MAX;
	q.push(s); dis[s]=0; vis[s]=1;
	while(!q.empty()) {
		int u=q.front();
		q.pop(); vis[u]=0; //出队,记得取消标记
		for(int i=0;i<G[u].size();i++) {
			int v=G[u][i].first,z=G[u][i].second;
			if(dis[v]>dis[u]+z) { //更新答案
				dis[v]=dis[u]+z;
				if(!vis[v]) {vis[v]=1; q.push(v);}
			}
		}
	}
}

int main(){
	n=read(),m=read(),s=read();
	for(int i=1;i<=m;i++) {
		int x=read(),y=read(),z=read();
		G[x].push_back(make_pair(y,z));
	} SPFA();
	for(int i=1;i<=n;i++) printf("%d ",dis[i]); //输出答案
	return 0;
}

首先要惊喜地告诉你,这就是广为人知的 SPFA \text{SPFA} SPFA 算法,你已经学会了一个中级图论算法。

那么,为什么会达到 O ( n 2 ) O(n^2) O(n2) 的时间复杂度?可能你会说,这应该是 O ( n ) O(n) O(n) 伴着大常数而已吧?

不是这样的。 SPFA \text{SPFA} SPFA 看似没有问题,但是 如果出题人构造数据可以卡到 O ( n 2 ) O(n^2) O(n2),只有随机数据才能侥幸通过,和另一个 dijkstra \text{dijkstra} dijkstra 的效率相比,不太稳定,随机图效率极高,手造图效率极低。考场上只有时间不够,骗分等才会用。

那么,这个极限数据是什么呢?

这里给出一组官方卡数据(来源于 CodeForces)

n 2*n-3 1
1 2 1000000000
1 3 1000000000
1 4 1000000000
...
1 n 1000000000
2 3 1
3 4 1
4 5 1
...
n-1 n 1

你会发现,假设你一步把 1 1 1 ~ n n n 所有数的最短路都标了出来。

然后,你就运用 “ k − 1 k-1 k1 k k k 之间的边开始不断更新”,然后呢就咕掉了。

所以出题人随随便便就可以把你卡掉(多简单?)

但是毕竟是随机数据嘛,没什么问题

但是请注意,如果你去 加强版 作死的话:

你将会得到一个 32分 TLE

所以,那道加强版需要用 dijkstra \text{dijkstra} dijkstra 加上堆优化啦。。

你可能感兴趣的:(图,最短路)