图的最短路专题——洛谷题单

题目

传送门

P1119灾后重建

题目大意:在村庄重建好之前,所有与未重建完成的村庄的公路均无法通车。只有连接着两个重建完成的村庄的公路才能通车,只能到达重建完成的村庄。对每一个询问(x,y,t)输出对应的答案,即在第t天,从村庄x到村庄y的最短路径长度为多少。

思路:那么需要理解Floyd算法的核心:通过其他的点进行中转来求的两点之间的最短路。
所有的边全部给出,按照时间顺序更新每一个可用的点(即修建好村庄),对于每个时间点进行两点之间询问,求对于目前建设的所有村庄来说任意两点之间的最短路

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
#define MAXSIZE 1e9
int g[205][205];
int Time[205];
int n,m;//村庄,公路数量
void updata(int k)
{
    
    //k为Floyd分割点
	for (int i = 0; i < n; i++)
	{
    
    
		for (int j = 0; j < n; j++)
		{
    
    
			if (g[i][j] > g[i][k] + g[k][j])
				g[i][j] = g[i][k] + g[k][j];
		}
	}
}
int main()
{
    
    
	cin >> n >> m;
	for (int i = 0; i < n; i++)
		cin >> Time[i];
	for (int i = 0; i < n; i++)
	{
    
    
		for (int j = 0; j <= n; j++)
			g[i][j] = g[j][i] = MAXSIZE;
	}
	int x, y, w,t;
	for (int i = 1; i <= m; i++)
	{
    
    
		cin >> x >> y >> w;
		g[x][y] = g[y][x] = w;
	}
	int q,now=0;//询问次数,当前要更新的点
	cin >> q;
	while (q--)
	{
    
    
		cin >> x >> y >> t;
		while (now < n && Time[now] <= t)
		{
    
    //依次遍历看是否可以更新
			updata(now);
			now++;
		}
		if (Time[x] > t || Time[y] > t)
		//该村庄没有重建,直接-1
			cout << "-1" << endl;
		else if (g[x][y] == MAXSIZE)
			cout << "-1" << endl;
		else
			cout << g[x][y] << endl;
	}
}

P3371单源最短路径(弱化版)

给出一个有向图,请输出从某一点出发到所有点的最短路径长度。
对于数据:1≤n≤1000,1≤m≤100000

SPFA或者dijkstra+优先队列优化(这个做法通用)

#include<iostream>
#include<algorithm>
#include<vector>
#include <queue>
#include<map>
using namespace std;
#define maxn 500005
#define INF 2147483647
long long int n, m, s,cnt;//n个点,m条边
long long int vis[maxn];
long long int dis[maxn];
struct Edge
{
    
    
	int to, w, next;//终点,边权,同起点的上一条边的编号
}edge[maxn];//边集
long long int head[maxn];//head[i],表示以i为起点的第一条边在边集数组的位置(编号)
void init()//初始化
{
    
    
	for (int i = 0; i <= n; i++) 
		head[i] = -1;
	cnt = 0;
}
void add_edge(int u, int v, int w)//加边,u起点,v终点,w边权
{
    
    
	edge[++cnt].to = v; //终点
	edge[cnt].w = w; //权值
	edge[cnt].next = head[u];//以u为起点上一条边的编号,也就是与这个边起点相同的上一条边的编号
	head[u] = cnt;//更新以u为起点上一条边的编号
}
void spfa()//以s为源点
{
    
    
	queue< int > q;
	for (int i = 1; i <= n; i++)
	{
    
    
		vis[i] = 0;
		dis[i] = INF;
	}
	dis[s] = 0;//源点入队
	q.push(s);
	vis[s] = 1;
	while (!q.empty())
	{
    
    
		int h = q.front();
		q.pop();
		vis[h] = 0;
		for (int i = head[h]; i; i = edge[i].next)
		{
    
    
			if (dis[edge[i].to] > dis[h] + edge[i].w)
			{
    
    
				dis[edge[i].to] = dis[h] + edge[i].w;
				if (!vis[edge[i].to])
				{
    
    
					q.push(edge[i].to);//入队
					vis[edge[i].to] = 1;
				}
			}
		}
	}
}
int main()
{
    
    
	cin >> n >> m >> s;
	init();
	for (int i = 1; i <= m; i++)
	{
    
    
		int x, y, z;
		cin >> x >> y >> z;
		add_edge(x, y, z);
	}
	spfa();
	for (int i = 1; i <= n; i++)
		cout << dis[i] << " ";
}


P1629邮递员送信

有一个邮递员要送东西,邮局在节点 1。他总共要送 n−1 样东西,其目的地分别是节点 2 到节点 n。 所有的道路都是单行的,共有 m 条道路。这个邮递员每次只能带一样东西,并且运送每件物品过后必须返回邮局。求送完东西并且最终回到邮局最少需要的时间。

正着来一遍dijkstra()+优化,单源最短路径。反着是多源单终点的最短路径,可以多次dijkstra()。
其实还有一个思路:用1次找各个点到点1的最短路,然后开一个反向图,再搜一下点1到反向图各个点的最短路这时我们发现反向图中点1到各个点的最短路就是普通图中各个点到点1的最短路。

#include<iostream>
#include<algorithm>
#include <vector>
#include<queue>
#include <cstring>
using namespace std;
#define maxsize 10005
int vis[maxsize];
long long int dis[maxsize];
vector < int > g[maxsize];
vector < int > w[maxsize];
priority_queue<pair<int, int> > q;//距离,点
void dijkstra(int s)
{
    
    //源点
	memset(vis, 0, sizeof(vis));
	memset(dis, 0x3f, sizeof(dis));//最大值初始化
	dis[s] = 0;//注意这里别标记源点,到循环内标记
	q.push(make_pair(dis[s], s));
	while (!q.empty())
	{
    
    
		int head = q.top().second;
		q.pop();
		if (vis[head] == 1)
			continue;
		vis[head] = 1;
		for (int i = 0; i < g[head].size(); i++)
		{
    
    
			int v = g[head][i];//去的点
			int we = w[head][i];//权值
			if (dis[v] > dis[head] + we)
			{
    
    
				dis[v] = dis[head] + we;
				q.push(make_pair(-dis[v], v));
			}
		}
	}
}
int main()
{
    
    
	int n, m;
	cin >> n >> m;
	int x, y, z;
	for (int i = 1; i <= m; i++)
	{
    
    
		cin >> x >> y >> z;
		g[x].push_back(y);
		w[x].push_back(z);
	}
	long long int ans = 0;
	dijkstra(1);
	for (int i = 1; i <= n; i++)
		ans += dis[i];
	for (int i = 2; i <= n; i++)
	{
    
    
		dijkstra(i);
		ans += dis[1];
	}
	cout << ans << endl;
}

P4779单源最短路径(标准版)dijkstra+优先队列优化

给定一个 n 个点,m 条有向边的带非负权图,请你计算从 s 出发,到每个点的距离。数据保证你能从 s 出发到任意点。

传统Dijkstra,在选取中转站时,是遍历取当前最小距离节点,但是我们其实可以利用小根堆(STL的priority_queue)优化这个过程,从而大大降低复杂度。

#include<iostream>
#include<algorithm>
#include <vector>
#include<queue>
#include <cstring>
using namespace std;
#define inf 0x7f
int dis[100001];
int vis[100001];
struct edge
{
    
    
	int v;
	int w;
};
vector < edge > g[100001];
int n, m, s;
void dijkstra()
{
    
    
	memset(dis, inf, sizeof(dis));
	dis[s] = 0;
	priority_queue< pair<int, int> > q;//优先队列
	q.push(make_pair(0, s));//前一个数是dis[u],后一个是u
	while (!q.empty())
	{
    
    
		int head = q.top().second;
		q.pop();
		if (vis[head] == 1)
			continue;//访问过直接跳过重新开始
		vis[head] = 1;
		for (int i = 0; i < g[head].size(); i++)
		{
    
    
			int v = g[head][i].v;
			int w= g[head][i].w;
			if (dis[head] + w < dis[v])
			{
    
    
				dis[v] = dis[head] + w;
				if (!vis[v])
				{
    
    
					q.push(make_pair(-dis[v], v));//入队 
				}
			}
		}
	}
}
int main()
{
    
    
	cin >> n >> m >> s;
	for (int i = 1; i <=m; i++)
	{
    
    
		int u;
		edge node; 
		cin >> u >> node.v >> node.w;
		g[u].push_back(node);
	}
	dijkstra();
	for(int i=1;i<=n;i++)
		cout << dis[i] << " ";
}

注意:下面优先队列每次要找一个dis最小的,而每次优先队列弹出的是最大值,反过来后,找到的是最靠近0的负数,对应最小值。若存的不是负数,就会找到最大值;存负数就会找到最小值。

P1144最短路计数

给出一个N个顶点M条边的无向无权图,顶点编号为1−N。问从顶点1开始,到其他每个点的最短路有几条。数据量在百万以上。

所有的边权都为1,所以一个点的最短路就相当于是它在BFS搜索树中的深度。一个点最短路一定经过了一个层数比它少一的结点(否则不是最短路)。所以用每个相邻且层数比当前结点层数少一的点更新当前点的路径跳数即可。

#include<iostream>
#include<algorithm>
#include <vector>
#include<queue>
using namespace std;
#define maxsize 1000005
int vis[maxsize];
vector < int > g[maxsize];
int dep[maxsize];
int num[maxsize];
int main()
{
    
    
	long long int  n,m;
	cin >> n >> m;
	long int x, y;
	for (long long int i = 1; i <= m; i++)
	{
    
    
		cin >> x >> y;
		g[x].push_back(y);
		g[y].push_back(x);
	}
	num[1] = 1;
	vis[1] = 1;
	dep[1] = 0;
	queue < int > q;
	q.push(1);
	while (!q.empty())
	{
    
    
		long int head = q.front();
		q.pop();
		for (int i = 0; i < g[head].size(); i++)
		{
    
    
			int v = g[head][i];
			if (!vis[v])
			{
    
    
				vis[v] = 1;
				dep[v] = dep[head] + 1;
				q.push(v);
			}
			if (dep[v] == dep[head] + 1)
			{
    
    
				num[v] = (num[v] + num[head]) % 100003;
			}
		}
	}
	for (int i = 1; i <= n; i++) 
	{
    
    
		cout << num[i] << endl;
	}
}

P1462 通往奥格瑞玛的道路

有n个城市。城市之间有m条双向的公路,从某个城市到另一个城市,会遭到联盟的攻击,进而损失一定的血量。每次经过一个城市,都会被收取一定的过路费。假设1为暴风城,n为奥格瑞玛,而他的血量最多为b,出发时他的血量是满的。他想知道,在可以到达奥格瑞玛的情况下,他所经过的所有城市中最多的一次收取的费用的最小值是多少。

出现最多的一次收取的最小值,那这就是二分法,上边界:不难想到,每个方案无论怎样,它的答案一定不会于F[N]的最大值,因此上边界为F数组的最大值。损失的血量就是边权,每走一次收费都小于mid的最短路查看剩余血量是否符合要求。dijkstra+二分+优化

#include<iostream>
#include <set>
#include<algorithm>
#include <vector>
#include<queue>
#include <cstring>
using namespace std;
#define maxsize 10005
vector < pair< int, long long> > g[maxsize];
int vis[maxsize];
long long dis[maxsize];
priority_queue < pair< long long,int > > q;
long long f[maxsize];
long long int n, m, b;
int check(int x)
{
    
    
	if (f[1] > x)return 0;
	for (int i = 1; i <= n; i++) 
	{
    
    
		dis[i] = 1e18;//必备的初始化,没有会致错 
		vis[i] = 0;
	}
	dis[1] = 0;
	q.push(make_pair(0, 1));//修改的dijk 
	while (!q.empty())
	{
    
    
		int head = q.top().second;
		q.pop();
		if (head == n)
		{
    
    
			if (dis[head] >= b)return 0;
			else return 1;
		}
		if (vis[head])continue;
		vis[head] = 1;
		for (int i = 0; i < g[head].size(); i++)
		{
    
    
			int v= g[head][i].first;
			if (f[v] > x)continue;
			long long w = g[head][i].second;
			if (dis[v] > dis[head] + w)
			{
    
    
				dis[v] = dis[head] + w;
				q.push(make_pair(-dis[v], v));
			}
		}
	}
	return 0;//没有到达
}
int main()
{
    
    
	long long maxn = -1;
	cin >> n >> m >> b;
	long long int x, y, z;
	for (int i = 1; i <= n; i++)
	{
    
    
		cin >> f[i];
		maxn = max(maxn, f[i]);//上边界
	}
	for (int i = 1; i <= m; i++)
	{
    
    
		cin >> x >> y >> z;
		g[x].push_back(make_pair(y, z));
		g[y].push_back(make_pair(x, z));
	}
	long long ans = -1,l=1,r=maxn;
	while (l <= r)
	{
    
    
		long long mid = (l + r) / 2;
		if (check(mid))
		{
    
    
			ans = mid;
			r = mid - 1;
		}
		else
			l = mid + 1;
	}
	if (ans == -1)
		cout << "AFK" << endl;
	else
		cout << ans << endl;
}

猜你喜欢

转载自blog.csdn.net/weixin_45605341/article/details/107708828