最短路习题(一)

1.骑车比赛

测试输入:

5 6
1 2 2
2 3 3
2 5 5
3 4 2
3 5 1
4 5 1

测试输出:

6

裸题,dijkstra

代码如下:

package _最短路习题;

import java.util.Arrays;
import java.util.Scanner;

public class _汽车比赛 {

	static int n, m;
	static int INF = 300;
	static int[][] g;
	static boolean[] vis;
	static int[] dis;

	static void dijkstra(int s) {
		dis[s] = 0;
		for (int i = 0; i < n; i++) {
			int mind = Integer.MAX_VALUE, minj = -1;
			for (int j = 0; j < n; j++) {
				if (!vis[j] && dis[j] < mind) {
					mind = dis[j];
					minj = j;
				}
			}
			if (minj == -1)
				break;
			vis[minj] = true;
			for (int j = 0; j < n; j++) {
				if (g[minj][j] != -1) {
					if (!vis[j] && dis[j] > dis[minj] + g[minj][j]) {
						dis[j] = dis[minj] + g[minj][j];
					}
				}
			}

		}
	}

	public static void main(String[] args) {
		// TODO Auto-generated method stub

		Scanner sc = new Scanner(System.in);
		n = sc.nextInt();
		m = sc.nextInt();
		g = new int[n][n];
		vis = new boolean[n];
		dis = new int[n];
		for (int i = 0; i < n; i++) {
			for (int j = 0; j < n; j++) {
				g[i][j] = -1;
			}
		}
		for (int i = 0; i < n; i++) {
			g[i][i] = 0;
		}

		Arrays.fill(dis, INF);
		for (int i = 0; i < m; i++) {
			int u = sc.nextInt() - 1;
			int v = sc.nextInt() - 1;
			int w = sc.nextInt();
			g[u][v] = g[v][u] = w;
		}
		dijkstra(0);
		System.out.println(dis[n-1]);
	}

}

 2.迷阵突围

测试输入:

3 3
1 1
2 2
3 2
1 2
2 3
1 3

测试输出:

2.41

次短路的介绍:

 两种次短路:对于一个带权图,求两个顶点的次短路,次短路表示除最短路以外长度最小的路径。

次短路可以分为2种,第一种次短路是可以重复经过一个点,第二种是次短路不能重复经过一个点。

对于第一种可以重复经过一个点的次短路,分别用dis0和dis1来记录最短路和次短路。每次更新时依次判断是否可以更新次短路和最短路的值。设最短路长为t,那么路径上的最短边为d,那么次短路即为t+2*d.由于需要计算次短路,所以至少需要2n-1次循环。

对于不可重复经过一个点的次短路,只需要记录最短路的路径,每次去掉一条边求最短路,枚举路径上的每条边,就可以求出次短路。本题便是不可重复经过一个点的次短路。

 

可重复的次短路代码:

static int[] dis;//源点到各点的距离,最短路
static int min=INF;
static boolean[] vis;//标记是否访问过
static LinkedList<Node> list;//邻接表
static 
static void dijkstra(int s){//源点:s
	Arrays.fill(dis,INF);
	dis[s]=0;
	for(int i=0;i<n;i++){
		int mind=INF,minj=-1;
		for(int j=0;j<n;j++){
			if(!vis[j]&&dis[j]<mind){
				mind=dis[j];
				minj=j;
			}
		}
		if(minj==-1)
			return;
		vis[minj]=true;
		for(int j=0;j<list[minj].size();j++){
			int v=list[minj].get(j).v;
			int w=list[minj].get(j).w;
			if(!vis[v]&&dis[v]>dis[minj]+w){
				if(min>w){
					min=w;
				}
				dis[v]=dis[minj]+w;
			}
		}
	}
	dis[n-1]=dis[n-1]+2*min;//次短路长
}
class Node{
	int v,w;
	public Node(int v,int w){
		this.v=v;
		this.w=w;
	}
}

本题不可重复的次短路代码:

package _最短路习题;

import java.util.Arrays;
import java.util.LinkedList;
import java.util.Scanner;

//次短路径
//解题说明:不允许重复节点的次短路径,先dijksta算法算出最短路径,并记录1—>n最短路的路径。
public class _迷阵突围 {
	static int n, m, maxn = 205;
	static int[] x = new int[maxn];//x点
	static int[] y = new int[maxn];//y点
	static double[][] mat = new double[maxn][maxn];// mat[u][v]表示 u-->v 的距离
	static double[] dis = new double[maxn];// 标记源点到其他点的距离
	static boolean[] vis = new boolean[maxn];// 标记顶点是否访问过
	static int[] pre = new int[maxn];// 记录路径的前驱
	static int[] temppre = new int[maxn];

	// 求两点之间距离
	static double distance(int i, int j) {
		double t1 = (x[i] - x[j]) * (x[i] - x[j]);
		double t2 = (y[i] - y[j]) * (y[i] - y[j]);
		return Math.sqrt(t1 + t2);
	}

	// dijkstra
	static void dij(int s) {
		for (int i = 1; i <= n; i++) {
			pre[i] = -1;
			dis[i] = 1e20;
			vis[i]=false;
		}
		dis[s] = 0;
		for (int i = 1; i <= n; i++) {
			double mind = 1e20;
			int minj = -1;
			for (int j = 1; j <= n; j++) {
				if (!vis[j] && dis[j] < mind) {
					mind = dis[j];
					minj = j;
				}
			}
			if (minj == -1)
				return;
			vis[minj] = true;
			for (int j = 1; j <= n; j++) {
				if (!vis[j] && dis[j] > dis[minj] + mat[minj][j]) {
					pre[j] = minj;// 记录路径前驱
					dis[j] = dis[minj] + mat[minj][j];
				}
			}

		}
	}

	public static void main(String[] args) {
		// TODO Auto-generated method stub

		Scanner sc = new Scanner(System.in);
		n = sc.nextInt();
		m = sc.nextInt();
		int u = 0, v = 0;
		for (int i = 1; i <= n; i++) {
			x[i] = sc.nextInt();
			y[i] = sc.nextInt();
		}
		for (int i = 1; i <= n; i++) {
			for (int j = 1; j <= n; j++) {
				mat[i][j] = 1e20;
			}
		}
		while (m-- > 0) {
			u = sc.nextInt();
			v = sc.nextInt();
			mat[u][v] = mat[v][u] = distance(u, v);
		}
		dij(1);
		if (dis[n] >= 1e20) {
			System.out.println(-1);
			return;
		}
		// temppre和pre一样
		for (int i = 0; i <= n; i++) {
			temppre[i] = pre[i];
		}
		double ans = 1e20;
		int now = n;
		// temppre[now]表示的是最短路径上now的前驱
		//枚举路径上的边
		while ((temppre[now] + 1) != 0) {// temppre[0]=-1
			mat[now][temppre[now]] = mat[temppre[now]][now] = 1e20;//把枚举的边切掉
			dij(1);
			ans = Math.min(ans, dis[n]);// 取最小
			mat[now][temppre[now]] = mat[temppre[now]][now] = distance(now, temppre[now]);
			now = temppre[now];// 节点依次往前,取路径上的前一个顶点
		}
		if (ans >= 1e20) {
			System.out.println(-1);
			return;
		} else {
			System.out.println(String.format("%.2f", ans));
		}
	}
}

3.圣诞树

测试输入:

4 4
10 20 30 40
1 2 3
2 3 2
1 3 5
2 4 1

输出:370

其实这里就牵扯到最短路的一类问题,这类问题看似是生成树问题但其实是最短路问题,

原因就在于边权的定义方式。先来想一种简单的情况,点的边权为1,或者说点没有边权,
                                                                                           
对于<i,j>(i是j的父亲),Ve=cntj*We(cnt是j所在子树的结点个数),我们可以用单源最短路算法求出树根到所有节点的最短路,所有结点最短路之和就是答案。为什么呢?

题目要求构造一棵树,那么考虑从树根分别走到各个结点的路径长度之和,则<i,j>的贡献就是cntj*We,因为每要走到j的子树中的一个点就要经过一次<i,j>,而这刚好是边权的定义。那么如何最小化整棵树的边权之和呢?其实就是最小化树根到每个结点的路径长度之和,

如果树根确定,只需以树根为源点,跑一遍单源最短路,然后将各个结点的最短路累加起来,

如果树根不确定,就需要对每个树根都求一遍到其他结点最短路之和,取最小值。

回到原题,每个结点都有权值,其实也很好想,每条边对于答案的贡献都扩大了,比如某一结点的权值为w,先只考虑到这个点的路径,都相当于由原来只经过一次变为经过w次,

也就是说,可以把权值为w的点看成没有权值的w个在相同位置的点,在统计答案时,只需将ans+=d[i]修改为ans+=w[i]*d[i]。

 多读几遍就能理解了题意。

代码如下:

package _最短路习题;

import java.util.LinkedList;
import java.util.Scanner;

public class _圣诞树 {

	static int n, m, INF = Integer.MAX_VALUE;
	static int[] W;//每个节点的权值
	static int[] dis;//最短路
	static boolean[] vis;//节点是否访问过
	static LinkedList<Point4>[] list;//邻接表

	//初始化
	static void init() {
		list = new LinkedList[n];
		for (int i = 0; i < n; i++)
			list[i] = new LinkedList<Point4>();
		W = new int[n];
		dis = new int[n];
		for (int i = 0; i < n; i++)
			dis[i] = INF;
		vis = new boolean[n];
	}

	//dijkstra
	static void dij(int s) {
		dis[s] = 0;

		for (int i = 0; i < n; i++) {
			int mind = INF, minj = -1;
			for (int j = 0; j < n; j++) {
				if (!vis[j] && dis[j] < mind) {
					mind = dis[j];
					minj = j;
				}
			}
			if (minj == -1)
				return;
			vis[minj] = true;
			for (int j = 0; j < list[minj].size(); j++) {
				int v = list[minj].get(j).v;
				int w = list[minj].get(j).w;
				if (!vis[v] && dis[v] > dis[minj] + w) {
					dis[v]=dis[minj]+w;
				}
			}
		}
	}

	public static void main(String[] args) {
		// TODO Auto-generated method stub

		Scanner sc = new Scanner(System.in);
		n = sc.nextInt();
		m = sc.nextInt();
		init();
		for (int i = 0; i < n; i++) {
			W[i] = sc.nextInt();
		}
		int u, v, w;
		for (int i = 0; i < m; i++) {
			u = sc.nextInt() - 1;
			v = sc.nextInt() - 1;
			w = sc.nextInt();
			list[u].add(new Point4(v, w));
			list[v].add(new Point4(u, w));
		}
		dij(0);
		for(int i=0;i<n;i++) {
			if(dis[i]==INF) {
				System.out.println("No Answer");
				return;
			}
		}
		long ans = 0;
		for(int i=0;i<n;i++) {
			ans+=W[i]*dis[i];
		}
		System.out.println(ans);
	}

}

class Point4 {
	int v, w;

	public Point4(int v, int w) {
		this.v = v;
		this.w = w;
	}
}

4.时光机

输入数据:

3 5
2 1 3
3 2 -6
3 2 1
1 3 2
2 1 8

输出数据:

Yes

简单的spfa判断负环模板题

代码如下:


import java.util.Arrays;
import java.util.LinkedList;
import java.util.Queue;
import java.util.Scanner;


public class Main {
	static int n, m, maxn = 1005, INF = Integer.MAX_VALUE;
	static int[] dis = new int[maxn];
	static int[] in = new int[maxn];
	static boolean[] vis = new boolean[maxn];
	static LinkedList<Point5>[] list = new LinkedList[maxn];

	static boolean bspfa(int u) {
		Arrays.fill(dis, INF);
		dis[u] = 0;
		Queue<Integer> q = new LinkedList<Integer>();
		q.offer(u);
		vis[u] = true;

		while (!q.isEmpty()) {
			u = q.poll();
			vis[u] = false;
			for (int j = 0; j < list[u].size(); j++) {
				int v = list[u].get(j).v;
				int w = list[u].get(j).w;
				if (dis[v] > dis[u] + w) {
					dis[v] = dis[u] + w;
					if (!vis[v]) {
						q.offer(v);
						vis[v] = true;
						in[v]++;
						if (in[v] > n)
							return true;
					}
				}
			}
		}
		return false;
	}

	public static void main(String[] args) {
		// TODO Auto-generated method stub

		Scanner sc = new Scanner(System.in);
		n = sc.nextInt();
		m = sc.nextInt();
		for (int i = 0; i <= n; i++)
			list[i] = new LinkedList<>();
		int u = 0, v = 0, w = 0;
		for (int i = 0; i < m; i++) {
			u = sc.nextInt();
			v = sc.nextInt();
			w = sc.nextInt();
			list[u].add(new Point5(v, w));
		}
		boolean f=bspfa(1);

		if(f) {
			System.out.println("Yes");
		}else {
			System.out.println("No");
		}
	}

}

class Point5 {
	int v, w;

	public Point5(int v, int w) {
		this.v = v;
		this.w = w;
	}
}

5.闯关游戏

输入数据:

5
0 1 2
-60 1 3
-60 1 4
20 1 5
0 0

输出数据:

No
用spfa判断负环,如果负环存在,那么把这个点的值设置为无穷大,

代码如下:


import java.util.Arrays;
import java.util.LinkedList;
import java.util.Queue;
import java.util.Scanner;

public class Main {

	static int maxn = 110, num = 0;
	static int[] w = new int[maxn];// 权值。。。边
	static int[] value = new int[maxn];// 点权值
	static boolean[] vis = new boolean[maxn];// 是否标记过
	static int[] cnt = new int[maxn];// 记录入队次数

	static LinkedList<Integer>[] list = new LinkedList[maxn];

	static void init() {
		for (int i = 0; i < maxn; i++)
			list[i] = new LinkedList<Integer>();
	}

	static void add(int st, int en) {
		list[st].add(en);
	}

	static boolean bspfa(int st, int n) {
		Queue<Integer> q = new LinkedList<>();
		q.offer(st);
		vis[st] = true;
		cnt[st]++;
		value[st] = 100 + w[st];//st 的地图上可以获取的体力为value[st];

		while (!q.isEmpty()) {
			int u = q.poll();
			if (cnt[u] == n + 1) {
				value[u] = Integer.MAX_VALUE;
			}
			if (u == n)         //已经走出地图
				return true;
			vis[u] = false;

			for (int j = 0; j < list[u].size(); j++) {
				int v = list[u].get(j);
				if (value[v] < value[u] + w[v]) {
					value[v] = value[u] + w[v];
					if (!vis[v]) {
						q.offer(v);
						vis[v] = true;
					}
				}
			}

		}
		return false;
	}

	public static void main(String[] args) {
		// TODO Auto-generated method stub

		Scanner sc = new Scanner(System.in);
		int n = sc.nextInt();
		init();
		for (int i = 1; i <= n; i++) {
			w[i] = sc.nextInt();
			int k = sc.nextInt();
			while (k-- > 0) {
				int d = sc.nextInt();
				add(i, d);
			}
		}
		if (bspfa(1, n)) {
			System.out.println("Yes");
		} else {
			System.out.println("No");
		}
	}

}
发布了133 篇原创文章 · 获赞 31 · 访问量 2万+

猜你喜欢

转载自blog.csdn.net/qq_41921315/article/details/89279494