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