一、题目描述
最近发现了如何在Y-Crate游戏设备上运行开源软件。许多有进取心的设计师已经开发出部署在Y-Crate上的Advent风格的游戏。您的工作是测试许多这样的设计,看看哪些是可以取胜的。
每场比赛由多达100个房间组成。其中一个房间是起点,另一个房间是终点。每个房间的能量值都在-100到+100之间。单行门相互连接成对的房间。
玩家在起跑室开始时有 100 个能量点。她可以穿过连接她所在房间和另一个房间的任何门道,从而进入另一个房间。这个房间的能量值加到玩家的能量上。这一过程一直持续到她进入终点室获胜,或因精力耗尽而死亡(或沮丧地退出)。在她的冒险过程中,玩家可能会多次进入同一个房间,每次都会接收到它的能量。
能到达终点 n 输出 winnable,否则输出 hopeless
二、题解
方法一:SPFA 走正环
思路
题意:从一个开始有 100 的初始能量房间开始跑,每经过一个房间加上此房间的能量值,能量为负或正,问能不能到达终点。
注:在求最长路的时候只能用 spfa 或者是 Floyd 不能用基于贪心思想的 Dijkstra。
WA:一开始想法很简单,如果图存在负环, SPFA 不会让结点入队多次,也就很好地处理了负环问题。遇到正环就一直获取环里的能量,直到能量足够支撑到达终点为止。
未知错误:
- Q1:为什么跑到足够多的能量 maxr 再出环,为什么不行呢?
A1:题目是没有保证一定有正环呀,能
新技巧:SPFA 处理正环时,将三角不等式改为以下代码即可:
Arrays.fill(dist, 0);
if (dist[to] < dist[v] + w)
dist[to] = dist[v] + w
import java.util.*;
import java.math.*;
import java.io.*;
public class Main{
static int V, E, maxv = 100 + 50;
static Edge[] edges;
static int tot;
static int[] head, cnt, dist;
static boolean[] inq;
static int INF = 0;
static int maxr = 100 * 100 + 50;
static void addEdge(int u, int v, int w) {
edges[++tot] = new Edge();
edges[tot].to = v;
edges[tot].w = w;
edges[tot].next = head[u];
head[u] = tot;
}
static boolean spfa(int S) {
Arrays.fill(dist, INF);
dist[S] = 100;
ArrayDeque<Integer> q = new ArrayDeque<>();
q.add(S);
inq[S] = true;
while (!q.isEmpty()) {
int v = q.poll();
inq[v] = false;
for (int i = head[v]; i!=0; i = edges[i].next) {
int to = edges[i].to, w = edges[i].w;
if (dist[to] < dist[v] + w && cnt[to] < maxr) {
dist[to] = dist[v] + w;
cnt[to]++;
if (inq[to])
continue;
if (!q.isEmpty() && dist[to] < dist[q.peek()]) {
q.addFirst(to);
} else {
q.addLast(to);
}
inq[to] = true;
}
}
}
return dist[V] != INF;
}
public static void main(String[] args) throws IOException {
Scanner sc = new Scanner(new BufferedInputStream(System.in));
BufferedWriter w = new BufferedWriter(new OutputStreamWriter(System.out));
while (true) {
V = sc.nextInt();
if (V == -1)
break;
dist = new int[maxv];
head = new int[maxv];
cnt = new int[maxv];
inq = new boolean[maxv];
edges = new Edge[maxv+50];
for (int i = 1; i <= V; i++) {
int cost = sc.nextInt();
E = sc.nextInt();
for (int j = 0; j < E; j++) {
int to = sc.nextInt();
addEdge(i, to, cost);
}
}
System.out.println(spfa(1) ? "winnable" : "hopeless");
}
}
static class Edge {
int to, w, next;
public Edge() {}
}
}
复杂度分析
- 时间复杂度: ,
- 空间复杂度: ,
方法二:Floyd + SPFA(未知错误)
- 如果出现 cnt[v] >= V 则代表有正环,然后直接判断 v 是否与终点连通即可。
- 否则,正常跑 spfa,最后返回 dist[V] > 0 即可。
import java.util.*;
import java.math.*;
import java.io.*;
public class Main{
static int V, E, maxv = 100 + 50;
static Edge[] edges;
static int tot;
static int[] head, cnt, dist;
static boolean[] inq;
static int INF = -0x3f3f3f3f;
static int maxr = 100 * 100 + 1000;
static boolean[][] dp;
static void addEdge(int u, int v, int w) {
edges[++tot] = new Edge();
edges[tot].to = v;
edges[tot].w = w;
edges[tot].next = head[u];
head[u] = tot;
}
static void floyd() {
for (int k = 1; k <= V; k++)
for (int i = 1; i <= V; i++)
for (int j = 1; j <= V; j++) {
if (dp[i][j] || (dp[i][k] && dp[k][j]))
dp[i][j] = true;
}
}
static boolean spfa(int S) {
Arrays.fill(dist, INF);
dist[S] = 100;
ArrayDeque<Integer> q = new ArrayDeque<>();
q.add(S);
inq[S] = true;
while (!q.isEmpty()) {
int v = q.poll();
inq[v] = false;
if (cnt[v] >= V)
return dp[v][V];
for (int i = head[v]; i!=0; i = edges[i].next) {
int to = edges[i].to, w = edges[i].w;
if (dist[to] < dist[v] + w && dist[v] + w > 0) {
dist[to] = dist[v] + w;
if (inq[to])
continue;
if (!q.isEmpty() && dist[to] < dist[q.peek()]) {
q.addFirst(to);
} else {
q.addLast(to);
}
// q.add(to);
inq[to] = true;
cnt[v]++;
}
}
}
return dist[V] != INF;
}
public static void main(String[] args) throws IOException {
Scanner sc = new Scanner(new BufferedInputStream(System.in));
BufferedWriter w = new BufferedWriter(new OutputStreamWriter(System.out));
while (true) {
V = sc.nextInt();
if (V == -1)
break;
dist = new int[maxv];head = new int[maxv]; cnt = new int[maxv];inq = new boolean[maxv]; edges = new Edge[maxv+50];dp = new boolean[maxv][maxv];
for (int i = 1; i <= V; i++) {
int cost = sc.nextInt();
E = sc.nextInt();
for (int j = 0; j < E; j++) {
int to = sc.nextInt();
addEdge(i, to, cost);
dp[i][to] = true;
}
}
floyd();
System.out.println(spfa(1) ? "winnable" : "hopeless");
}
}
static class Edge {
int to, w, next;
public Edge() {}
}
}
复杂度分析
- 时间复杂度: ,
- 空间复杂度: ,
方法三:一遍 spfa
- 对于入队次数大于 V 次的结点,我们将其距离源点的距离设置为 INF,这样做,环中的结点的值就不会更新。
- 这样做的后果有两个:
- 环中的每一个结点都不和外面的点连通,最后 dist[V] 的值将会是 -INF
- 环中的某些结点与外界(包括终点)是连通的,那么 dist[V] 的值将会被更新。
未知错误:没理由过不了的…
import java.util.*;
import java.math.*;
import java.io.*;
public class Main{
static int V, E, maxv = 100 + 50;
static Edge[] edges;
static int tot;
static int[] head, cnt, dist;
static boolean[] inq;
static int INF = 0x1fffffff;
static int[] p;
static void addEdge(int u, int v) {
edges[tot] = new Edge();
edges[tot].to = v;
edges[tot].next = head[u];
head[u] = tot++;
}
static boolean spfa(int S) {
Arrays.fill(dist, -INF);
dist[1] = 100;
ArrayDeque<Integer> q = new ArrayDeque<>();
q.add(1);
inq[1] = true;
cnt[1]++;
while (!q.isEmpty()) {
int v = q.poll();
inq[v] = false;
if (dist[v] + p[v] <= 0)
continue;
for (int i = head[v]; i!=0; i = edges[i].next) {
int to = edges[i].to, w = p[v];
if (dist[to] < dist[v] + w) {
dist[to] = dist[v] + w;
if (!inq[to]) {
cnt[to]++;
if (cnt[to] > V)
continue;
else if (cnt[to] == V)
dist[to] = INF;
q.add(to);
inq[to] = true;
}
}
}
}
return dist[V] > 0;
}
public static void main(String[] args) throws IOException {
Scanner sc = new Scanner(new BufferedInputStream(System.in));
BufferedWriter w = new BufferedWriter(new OutputStreamWriter(System.out));
while (true) {
V = sc.nextInt();
if (V == -1)
break;
dist = new int[maxv];head = new int[maxv]; cnt = new int[maxv];inq = new boolean[maxv]; edges = new Edge[maxv+50];
p = new int[maxv];
for (int i = 1; i <= V; i++) {
int cost = sc.nextInt();
p[i] = cost;
E = sc.nextInt();
for (int j = 0; j < E; j++) {
int to = sc.nextInt();
addEdge(i, to);
}
}
System.out.println(spfa(1) ? "winnable" : "hopeless");
}
}
static class Edge {
int to, next;
public Edge() {}
}
}
复杂度分析
- 时间复杂度: ,
- 空间复杂度: ,
最长路习题:https://www.luogu.com.cn/problem/P1807
https://blog.csdn.net/WilliamSun0122/article/details/77948311