Teoría de grafos: aplicación del camino más corto

    Este artículo registra principalmente varios temas sobre la ruta más corta, principalmente basados ​​en los algoritmos de Dijkstra y Floyd.Dado que Dijkstra no considera pesos de borde negativos, este artículo no considera el tema de los bordes negativos.


Tabla de contenido

optimización de Dijkstra

P1119 Reconstrucción de desastres

P1629 Cartero Entregando Cartas 

P1144 Recuento de ruta más corta

P1462 El Camino a Orgrimmar

P1522 [USACO2.4] Recorridos de vacas


 

optimización de Dijkstra

Este algoritmo es uno de los algoritmos más clásicos en el problema del camino más corto. Se utiliza para resolver el problema del camino más corto de fuente única, es decir, el camino más corto entre dos puntos. De hecho, puede encontrar el camino más corto desde un punto. a todos los demás puntos. La complejidad de escritura convencional es O (n ^ 2), puede consultar un blog que escribí antes: teoría de grafos --- árbol de expansión mínimo, ruta más corta_czc131's blog-CSDN blog_minimum árbol de expansión ruta más corta .

Revise el proceso de solución: primero, encuentre la ruta más corta y definida con la distancia actual y luego actualice la distancia desde otros puntos hasta el punto de inicio en función de este punto.

Hay dos puntos de optimización:

  1. Para encontrar el punto con la distancia actual más pequeña, puede pensar en la optimización del montón en este momento. Por supuesto, la aplicación real es usar la cola de prioridad de STL. Almacene el número de puntos en la cola y la distancia al punto de inicio. Al ingresar a la cola, la cola de prioridad automáticamente pondrá al más pequeño al frente de la cola según la distancia. En este momento, si saca el cabeza de la cola, debe ser la distancia más corta Establezca una marca vis Array, si la cabeza actual del equipo ha sido marcada como la ruta más corta, simplemente sáquela, de lo contrario, actualice, si la distancia mínima cambia durante la actualización proceso, debe agregarse a la cola, ya que puede convertirse en la ruta más corta actual.
  2. Es el proceso de actualización. Esta optimización generalmente tiene como objetivo almacenar con una matriz de adyacencia y, al mismo tiempo, hay menos bordes. Para el almacenamiento de matriz de adyacencia, muchos recorridos en realidad no son válidos durante la actualización y el almacenamiento perimetral es más eficiente en este momento. Introduce una estrella encadenada hacia adelante . La representación encadenada es una lista enlazada, y la dirección hacia adelante significa que el puntero apunta hacia el frente. Para cada borde, se establece una estructura, que se define de la siguiente manera:
struct Edge
{
	int to, next;
	double len;
}e[M];
int last[N], cnt;

Edge representa el borde, to representa el punto al que está conectado este borde, y next representa la posición del borde anterior que también es el punto inicial de este punto. Defina la última matriz para almacenar la posición del último borde en cada punto como punto de partida, similar a la lista enlazada El puntero principal, cnt registra el número de bordes, y el proceso de agregar bordes no tiene mucho que decir.

El proceso de actualización de la ruta más corta es similar al original, elimina el punto más corto y luego actualiza todos los puntos conectados a este punto.

Supongamos que para calcular el camino más corto desde k a todos los demás puntos, el código es el siguiente:

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] });
			}
	}
}

Aquí hay algunas aplicaciones relacionadas

P1119 Reconstrucción de desastres

Enlace temático: Reconstrucción posterior al desastre - Luogu 

    Lo más interesante de esta pregunta es que los pueblos han sido reparados uno tras otro, si no se reparan, no pueden pasar. Cuando se repara un pueblo , significa que se puede pasar este punto, entonces, ¿se puede usar este punto para relajar la distancia entre otros puntos ? Esta es en realidad la aplicación del algoritmo de Floyd. Para cada día de la consulta, utilice todos los puntos reparados antes de este día para actualizar la ruta más corta entre todos los demás puntos y, a continuación, genere la distancia entre los puntos consultados.

    Cometí un error al principio, es decir, solo las aldeas reparadas se actualizaron durante la actualización. De hecho, esto es incorrecto. Las aldeas no reparadas simplemente no están abiertas al tráfico. Bajo la condición de usar las aldeas reparadas actualmente, son no reparado El camino más corto entre aldeas en realidad ha cambiado , pero no se puede alcanzar, por lo que debe prestar atención.

    Al exportar, recuerde juzgar si se puede llegar a los dos pueblos y si uno se puede abrir al tráfico si no se repara.

el código se muestra a continuación:

#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 Cartero Entregando Cartas 

 Enlace al tema: El cartero entrega cartas - Luogu

    Este problema es para calcular la ruta más corta desde el primer punto a todos los puntos, y también para calcular la ruta más corta desde todos los puntos al primer punto. El primero es fácil de calcular, y el segundo solo necesita construir el mapa en inversa, entonces el problema también se transforma Para la distancia más corta desde el primer punto a todos los puntos, luego emite la respuesta y suma los dos. 

el código se muestra a continuación:

#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 Recuento de ruta más corta

 Enlace al tema: Recuento de rutas más cortas - Luogu

    Esta pregunta necesita calcular el número del camino más corto a cada punto. El camino más corto de un punto se pasa desde el punto anterior. Asumiendo que d[i] es i puntos, necesita tomar al menos d[i] pasos desde 1. Entonces d[i]=sum(d[j]==d[i]-1) , es decir, el número de pasos desde el punto anterior de este punto se pasa a este punto, y sumarlos es el número de caminos más cortos en este punto, la esencia de este proceso es bfs.

el código se muestra a continuación:

#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 El Camino a Orgrimmar

Enlace temático: El camino a Orgrimmar - Luogu 

    Esta pregunta necesita calcular el valor mínimo del valor máximo requerido para ir desde el punto inicial 1 hasta el punto final n, lo cual es muy complicado a primera vista. En primer lugar, debe quedar claro que debe pagar un peaje para pasar por una ciudad y debe pagar una deducción de sangre para caminar por una carretera.

    Teniendo en cuenta que cuanto mayor sea la cantidad, a más ciudades puedes ir, más caminos puedes elegir y mayor es la posibilidad de llegar al punto final, entonces aquí hay una monotonicidad obvia, por lo que la cantidad dicotómica, al calcular No pasar por ciudades que no cumplan con el camino más corto, es decir, el peaje es mayor que el resultado de 2 puntos.Trate la deducción de sangre como un lado derecho, y encuentre la respuesta verificando si la deducción mínima de sangre, que es decir, el camino más corto es menor que el volumen de sangre.

el código se muestra a continuación:

#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] Recorridos de vacas

 Tema de enlace: [USACO2.4] Cow Tours - Luogu   

    La cantidad de datos en esta pregunta es pequeña y puede soportar una complejidad relativamente alta, pero los pasos son relativamente engorrosos, como se indica a continuación.

  1. Primero pasar por dfs para marcar diferentes pastos y dividir las áreas, es decir, la operación de teñido.
  2. Ejecute Floyd una vez para calcular el camino más corto entre todos los puntos y luego enumere y calcule el valor máximo del camino máximo en el mismo pasto que el diámetro de cada pasto.
  3. Debido a que es necesario calcular el valor mínimo del diámetro después de conectar dos pastos cualesquiera, se enumera la combinación (i, j) de todos los puntos. Si pertenecen a pastos diferentes, entonces conecte estos dos puntos para ver el diámetro del nuevo Este diámetro tiene Hay dos posibilidades: una no pasa por el lado nuevo conectado, entonces es el valor mayor de los diámetros de los dos pastos originales, si pasa por el lado nuevo, entonces es la distancia máxima desde el punto de pasto i pertenece a i más el pasto j pertenece a La distancia máxima a j más la distancia entre i y j.
  4. Actualice dinámicamente la respuesta en el proceso de encontrar el borde anterior y, finalmente, emita el resultado.

el código se muestra a continuación:

#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;
}

 ❀❀❀Terminar espolvorear flores❀❀❀

Supongo que te gusta

Origin blog.csdn.net/weixin_60360239/article/details/128527691
Recomendado
Clasificación