Graph Theory--Application of the Shortest Path

    This article mainly records several topics about the shortest path, mainly based on Dijkstra and Floyd algorithms. Since Dijkstra does not consider negative edge weights, this article does not consider the issue of negative edges.


Table of contents

Dijkstra's optimization

P1119 Disaster reconstruction

P1629 Postman Delivering Letters 

P1144 Shortest path count

P1462 The Road to Orgrimmar

P1522 [USACO2.4] Cow Tours


 

Dijkstra's optimization

This algorithm is one of the most classic algorithms in the shortest path problem. It is used to solve the single-source shortest path problem, that is, the shortest path between two points. In fact, it can find the shortest path from one point to all other points. The conventional writing complexity is O(n^2), you can refer to a blog I wrote before: Graph theory---minimum spanning tree, shortest path_czc131's blog-CSDN blog_minimum spanning tree shortest path .

Review the solution process: First, find the shortest and most definite shortest path with the current distance, and then update the distance from other points to the starting point based on this point.

There are two optimization points:

  1. To find the point with the smallest current distance, you can think of heap optimization at this time. Of course, the actual application is to use the priority queue of STL. Store the number of points in the queue and the distance to the starting point. When entering the queue, the priority queue will automatically put the smallest one at the head of the queue according to the distance. At this time, if you take out the head of the queue, it must be the shortest distance. Set a vis Array mark, if the current head of the team has been marked as the shortest path, then just pop it off, otherwise update, if the minimum distance changes during the update process, it must be added to the queue, because it may become the current shortest path.
  2. Is the update process. This optimization is generally aimed at storing with an adjacency matrix, and at the same time there are fewer edges. For adjacency matrix storage, many traversals are actually invalid during update, and edge storage is more efficient at this time. Introduce a chained forward star . The chained representation is a linked list, and the forward direction means that the pointer points to the front. For each edge, a structure is established, which is defined as follows:
struct Edge
{
	int to, next;
	double len;
}e[M];
int last[N], cnt;

Edge represents the edge, to represents the point to which this edge is connected, and next represents the position of the previous edge that is also the starting point of this point. Define the last array to store the position of the last edge at each point as the starting point, similar to the linked list The head pointer, cnt records the number of edges, and the process of adding edges is not much to say.

The process of the shortest path update is similar to the original, take out the shortest point, and then update all the points connected to this point.

Suppose to calculate the shortest path from k to all other points, the code is as follows:

void dksa(int k)
{
	dis[k] = 0;
	q.push({ k, 0 });
	while(!q.empty())
	{
		node x = q.top();
		q.pop();
		int d = x.d;
		if (vis[d])continue;//如果已经处理过了那么不需要处理了
		vis[d] = 1;
		for (int i = last[d]; i; i = e[i].next)
			if (dis[e[i].to] > dis[d] + e[i].len)
			{
				dis[e[i].to] = dis[d] + e[i].len;
				q.push({ e[i].to,dis[e[i].to] });
			}
	}
}

Here are a few related applications

P1119 Disaster reconstruction

Topic Link: Post-disaster Reconstruction - Luogu 

    What is more interesting about this question is that the villages have been repaired one after another. If they are not repaired, they cannot pass through. When a village is repaired , it means that this point can be passed, so can this point be used to relax the distance between other points ? This is actually the application of the Floyd algorithm. For each day of the query, use all repaired points before this day to update the shortest path between all other points, and then output the distance between the queried points.

    I made a mistake at the beginning, that is, only the repaired villages were updated during the update. In fact, this is wrong. The unrepaired villages are just not open to traffic. Under the condition of using the currently repaired villages, they are not repaired . The shortest path between villages has actually changed , but it cannot be reached, so you need to pay attention.

    When exporting, remember to judge whether the two villages can be reached, and whether one can be opened to traffic if it is not repaired.

code show as below:

#include<stdio.h>
#include<algorithm>
using namespace std;
#define Inf 0x3f3f3f3f
#define N 201
int dis[N][N];//存储任意点之间的距离
int flag[N];//标记当前村庄是否已经修复完毕
int n, m;

struct Village
{
	int t;//村庄修复时间
	int num;//村庄号
}v[N];
int now;//表示更新到的索引


int cmp(Village x, Village y)
{
	return x.t < y.t;
}

void update(int day)
{
	for (int i = now; i < n && v[i].t <= day; i++)
		flag[i] = 1;//修复好当前村庄
	while (v[now].t <= day && now<n)
	{
		int k = v[now].num;
		//printf("k=%d\n", k);
		for (int i = 0; i < n; i++)
			for (int j = 0; j < n ; j++)//两个点都修复好才更新距离
				dis[i][j] = min(dis[i][j], dis[i][k] + dis[k][j]);
		now++;
	}
}

int main()
{
	scanf("%d%d", &n, &m);
	for (int i = 0; i < n; i++)
	{
		scanf("%d", &v[i].t);
		v[i].num = i;
	}
	sort(v, v + n, cmp);//将村庄按照修复时间排序
	int x, y, l;
	for (int i = 0; i < n; i++)
		for (int j = 0; j < n; j++)
			dis[i][j] = Inf;
	for (int i = 0; i < n; i++)
		dis[i][i] = 0;//自己到自己是0
	for (int i = 1; i <= m; i++)
	{
		scanf("%d%d%d", &x, &y, &l);
		dis[x][y] = min(dis[x][y], l);
		dis[y][x] = dis[x][y];
	}
	int q; scanf("%d", &q);
	int day = 0;//表示当前更新到的天数
	update(day);
	int t;
	for (int i = 1; i<= q; i++ )
	{
		scanf("%d%d%d", &x, &y, &t);
		if (t > day)
		{
			day = t;//更新到t这一天
			update(day);
		}
		if (!flag[x]||!flag[y]||dis[x][y]==Inf)//没有路或者未修复
			printf("-1\n");
		else printf("%d\n", dis[x][y]);
	}
	return 0;
}

P1629 Postman Delivering Letters 

 Topic Link: Postman Delivers Letters - Luogu

    This problem is to calculate the shortest path from the first point to all points, and also to calculate the shortest path from all points to the first point. The first one is easy to calculate, and the second one only needs to build the map in reverse, then the problem is also transformed For the shortest distance from the first point to all points, then output the answer and add the two together. 

code show as below:

#include<stdio.h>
#include<algorithm>
#include<queue>
using namespace std;
#define Inf 0x3f3f3f3f
#define N 1001
struct Edge
{
	int to,next;
	int len;
}e1[500001],e2[500001];
int last1[N], last2[N],cnt;
int dis1[N], dis2[N];
bool vis1[N], vis2[N];
int n, m;

struct Node
{
	int d, dis;
	inline bool operator <(const Node &x)const
	//重载运算符把最小的元素放在堆顶(大根堆)
	{
		return dis > x.dis;//这里注意符号要为'>'
	}
};
priority_queue<Node>q;

void add(Edge e[], int last[], int from, int to, int len)
{
	e[cnt].to = to;
	e[cnt].next = last[from];
	last[from] = cnt;
	e[cnt].len = len;
}

void dksa(int st, Edge e[], int last[], int dis[],bool vis[])
{
	dis[st] = 0;//重置不要忘记
	q.push({ st,0 });
	while (!q.empty())
	{
		Node node = q.top();
		q.pop();
		//printf("d=%d\n", node.d);
		int d = node.d;
		if (!vis[d])
		{
			vis[d] = 1;
			for (int i = last[d]; i; i = e[i].next)
			{
				if (dis[e[i].to] > dis[d] + e[i].len)
				{
					dis[e[i].to] = dis[d] + e[i].len;
					q.push({ e[i].to ,dis[e[i].to] });
				}
			}
		}
	}
}

int main()
{
	scanf("%d%d", &n, &m);
	int x, y, len;
	for (int i = 1; i <= m; i++)
	{
		scanf("%d%d%d", &x, &y, &len);
		cnt++;
		add(e1, last1, x, y, len);
		add(e2, last2, y, x, len);//反向建图
	}
	for (int i = 1; i <= n; i++)
		dis1[i] = Inf,dis2[i] = Inf;
	dksa(1, e1, last1,dis1,vis1);
	dksa(1, e2, last2, dis2,vis2);
	int ans = 0;
	for (int i = 2; i <= n; i++)
		ans += dis1[i] + dis2[i];
	printf("%d\n", ans);
	return 0;
}

P1144 Shortest path count

 Topic Link: Shortest Path Counting - Luogu

    This question needs to calculate the number of the shortest path to each point. The shortest path of a point is passed from the previous point. Assuming that d[i] is i points, it needs to take at least d[i] steps from 1 to reach. Then d[i]=sum(d[j]==d[i]-1) , that is, the number of steps from the previous point of this point is passed to this point, and adding them up is the number of shortest paths at this point , the essence of this process is bfs.

code show as below:

#include<stdio.h>
#include<algorithm>
#include<vector>
#include<queue>
using namespace std;
#define mod 100003
#define N 1000001
vector<int>v[N];
//queue<int>q;
int que[N];
int quetmp[N];
int n, m;
int ans[N];
bool vis[N];
void bfs(int x)
{
	ans[x] = 1;
	vis[x] = 1;
	int num = 0;
	que[++num] = x;
	while (num)
	{
		//printf("haha\n");
		//printf("num=%d\n", num);
		for (int i = 1; i <= num; i++)
		{
			x = que[i];
			//printf("x=%d\n", x);
			for (int i = 0; i < v[x].size(); i++)
				if (!vis[v[x][i]])//只给当前层的下一层传递,已经算过的点不会再算了,因为路径长度只会更长
					ans[v[x][i]] = (ans[v[x][i]] + ans[x]) % mod;
		}
		int num2 = 0;
		for (int i = 1; i <= num; i++)//整理接下来的队列
		{
			int x = que[i];
			for(int i=0;i<v[x].size();i++)
				if (!vis[v[x][i]])
				{
					quetmp[++num2] = v[x][i];
					vis[v[x][i]] = 1;
				}
		}
		num = num2;
		for (int i = 1; i <= num2; i++)
			que[i] = quetmp[i];
	}
}

int main()
{
	scanf("%d%d", &n, &m);
	int x, y;
	for (int i = 1; i <= m; i++)
	{
		scanf("%d%d", &x, &y);
		v[x].push_back(y);
		v[y].push_back(x);
	}
	bfs(1);
	for (int i = 1; i <= n; i++)
		printf("%d\n", ans[i]);
	return 0;
}

P1462 The Road to Orgrimmar

Topic Link: The Road to Orgrimmar - Luogu 

    This question needs to calculate the minimum value of the maximum value required to go from the starting point 1 to the end point n, which is very convoluted at first glance. First of all, it needs to be clear that you need to pay a toll to pass through a city, and you need to pay a blood deduction to walk a road.

    Considering that the larger the amount, the more cities you can go to, the more roads you can choose, and the greater the possibility of reaching the end point, so there is an obvious monotonicity here, so the dichotomous amount, when calculating Do not pass through cities that do not comply with the shortest path, that is, the toll is higher than the result of 2 points. Treat the deduction of blood as a side right, and find the answer by checking whether the minimum deduction of blood, that is, the shortest path is less than the blood volume.

code show as below:

#include<stdio.h>
#include<algorithm>
#include<queue>
using namespace std;
#define ll long long
#define N 10100
#define M 540000 //无向图要双倍数量!!!
#define Inf 0x7f7f7f7f
ll dis[N];//分别是1出发和n出发
bool vis[N];
int fee[N];

struct Edge
{
	int to, next;
	int len;
}e[M];
int last[N], cnt;
int n, m, b;

struct Node
{
	int d;
	ll dis;
	inline bool operator <(const Node &x)const
	//重载运算符把最小的元素放在堆顶(大根堆)
	{
		return dis > x.dis;//这里注意符号要为'>'
	}
};
priority_queue<Node>q;

void add(int from, int to, int len)
{
	e[++cnt].to = to;
	e[cnt].next = last[from];
	last[from] = cnt;
	e[cnt].len = len;
}

bool check(ll Max)
{
	for (int i = 1; i <= n; i++)
	{
		dis[i] = Inf;
		vis[i] = 0;
	}
	dis[1] = 0;
	q.push({ 1,0 });
	while (!q.empty())
	{
		Node x = q.top();
		int d = x.d;
		q.pop();
		if (vis[d])continue;
		vis[d] = 1;
		for (int i = last[d]; i; i = e[i].next)
		{
			if (fee[e[i].to] > Max)continue;//这个点不可以经过
			//printf("%d -> %d\n", d, e[i].to);
			if (dis[e[i].to] > dis[d] + (ll)e[i].len)
			{
				dis[e[i].to] = dis[d] + (ll)e[i].len;
				q.push({ e[i].to,dis[d] + (ll)e[i].len});
			}
		}
	}
	if (dis[n] <= b)return 1;//计算最小血量能不能达到终点
	else return 0;
}

int main()
{
	scanf("%d%d%d", &n, &m, &b);
	int x, y, z;
	for (int i = 1; i <= n; i++)
		scanf("%d", fee + i);//每个城市的费用
	for (int i = 1; i <= m; i++)
	{
		scanf("%d%d%d", &x, &y,&z);
		add(x, y, z);
		add(y, x, z);
	}
	ll l = fee[1], r = Inf;
	while (l <= r)
	{
		ll mid = (l + r) / 2;
		if (check(mid))r = mid - 1;
		else l = mid + 1;
	}
	if (l == Inf + 1)printf("AFK\n");
	else printf("%lld\n", l);
	return 0;
}

P1522 [USACO2.4] Cow Tours

 Topic link: [USACO2.4] Cow Tours - Luogu   

    The amount of data in this question is small, and it can withstand relatively high complexity, but the steps are relatively cumbersome, as follows.

  1. First go through dfs to mark different pastures and divide the areas, that is, the dyeing operation.
  2. Run Floyd once to calculate the shortest path between all points, and then enumerate and calculate the maximum value of the maximum road in the same pasture as the diameter of each pasture.
  3. Because it is necessary to calculate the minimum value of the diameter after connecting any two pastures, the combination (i, j) of all points is enumerated. If they belong to different pastures, then connect these two points to see the diameter of the new pasture. This diameter has There are two possibilities: one is not passing through the newly connected side, then it is the larger value of the diameters of the original two pastures; if it passes through the new side, then it is the maximum distance from the pasture point i belongs to to i plus the pasture j belongs to The maximum distance to j plus the distance between i and j.
  4. Dynamically update the answer in the process of finding the edge above, and finally output the result.

code show as below:

#include<stdio.h>
#include<algorithm>
#include<vector>
#include<math.h>
using namespace std;
#define Inf 0x3f3f3f3f
#define N 200
#define M N*N
double dis[N][N];//任意两点之间的最短距离
int color[N];//存储每个点所属的牧场类别
int colorNum;//记录牧场总数
double maxr[N];//记录每个牧场的直径
bool vis[N];
int n;

struct Edge
{
	int to, next;
	double len;
}e[M];
int last[N], cnt;

struct ZuoBiao
{
	int x, y;
}zuobiao[N];

double len(int x, int y)
{
	return sqrt(pow(zuobiao[x].x - zuobiao[y].x, 2) + pow(zuobiao[x].y - zuobiao[y].y, 2));
}

void add(int from, int to)
{
	e[++cnt].to = to;
	e[cnt].next = last[from];
	last[from] = cnt;
	e[cnt].len = sqrt(len(from, to));
}

void addColor(int x,int k)
{
	color[x] = k;
	for (int i = last[x]; i; i = e[i].next)
		if (!color[e[i].to])
			addColor(e[i].to, k);
}

int main()
{
	scanf("%d", &n);
	for (int i = 1; i <= n; i++)
		scanf("%d%d", &zuobiao[i].x, &zuobiao[i].y);
	for (int i = 1; i <= n; i++)
		for (int j = 1; j <= n; j++)
		{
			if (i == j)continue;
			dis[i][j] = Inf;//两点之间的最短距离
		}
	char ch;
	for (int i = 1; i <= n; i++)
		for (int j = 1; j <= n; j++)
		{
			scanf(" %c", &ch);
			if (ch == '1')
			{
				add(i, j);
				dis[i][j] = len(i, j);
			}
		}
	for (int i = 1; i <= n; i++)
		if (!color[i])
		{
			colorNum++ ;
			addColor(i,colorNum);//dfs染色
		}
	//计算最短路,同时记录每个牧场的直径
	for (int i = 1; i <= colorNum; i++)
		maxr[i] = 0;
	for (int k = 1; k <= n; k++)
		for (int i = 1; i <= n  ; i++)
			for (int j = 1; j <= n; j++)
			{
				if (i == j)continue;
				dis[i][j] = min(dis[i][j], dis[i][k] + dis[k][j]);
			}
	//记录每个牧场直径
	for (int i = 1; i <= n; i++)
		for (int j = 1; j <= n && j != i; j++)
			if (color[i] == color[j] && dis[i][j] != Inf)//同一色块并且有边
				maxr[color[i]] = max(maxr[color[i]], dis[i][j]);//如果是同一牧场那么更新直径
	double ans = Inf;//答案
	//枚举不同组合
	for(int i=1;i<=n;i++)
		for (int j = 1; j < i; j++)
		{
			if (color[i] == color[j])continue;//同一个牧场不考虑
			double ans1=0, ans2=0;//分别是第一个牧场经过i的最短路最大和第二个牧场经过j的最短路最大
			for (int k = 1; k <= n; k++)
			{
				if (color[k] == color[i] && dis[i][k] != Inf)
					ans1 = max(ans1, dis[i][k]);
				if (color[k] == color[j] && dis[j][k] != Inf)
					ans2 = max(ans2, dis[j][k]);
			}
			//考虑两种情况,不经过i,j,就是tmp,经过i,j,此时可能在i的也可能在j的
			double tmp = ans1 + ans2 + len(i, j);
			ans = min(ans,max(max(maxr[color[i]],maxr[color[j]]), tmp));
		}
	printf("%.6lf\n", ans);
	return 0;
}

 ❀❀❀End sprinkle flowers❀❀❀

Guess you like

Origin blog.csdn.net/weixin_60360239/article/details/128527691