江苏建设工程招投标网站市场调研报告怎么写范文
P5318 【深基18.例3】查找文献
解题思路
图的构建:使用邻接表存储每个文献的引用关系。读取输入后,对每个节点的邻接表进行排序和去重,以确保节点按升序排列。
DFS遍历:使用栈来实现非递归遍历。每次从栈中弹出节点后,将其邻接节点逆序压入栈中,以确保优先处理编号较小的节点。
BFS遍历:使用队列来实现广度优先搜索。按邻接表的顺序处理节点,确保先访问编号较小的节点。
结果输出:将DFS和BFS的遍历结果格式化为字符串并输出。
import java.util.*;
import java.io.*;public class Main {public static void main(String[] args) throws IOException {// 使用 StreamTokenizer 读取输入StreamTokenizer st = new StreamTokenizer(new BufferedReader(new InputStreamReader(System.in)));st.nextToken();int n = (int) st.nval; // 读取文章数量 nst.nextToken();int m = (int) st.nval; // 读取引用关系数量 m// 初始化邻接表,用于存储图的引用关系List<List<Integer>> adj = new ArrayList<>(n + 1);for (int i = 0; i <= n; i++) {adj.add(new ArrayList<>());}// 读取引用关系并存储到邻接表中for (int i = 0; i < m; i++) {st.nextToken();int X = (int) st.nval; // 文章 Xst.nextToken();int Y = (int) st.nval; // 文章 Yadj.get(X).add(Y); // 表示 X 指向 Y}// 对每个邻接表进行排序和去重,保证编号较小的文章优先访问for (int i = 1; i <= n; i++) {List<Integer> list = adj.get(i);if (list.isEmpty()) continue; // 如果没有引用关系,跳过Collections.sort(list); // 对邻接表排序// 去重操作int j = 0;for (int k = 1; k < list.size(); k++) {if (!list.get(k).equals(list.get(j))) {j++;list.set(j, list.get(k));}}if (list.size() > j + 1) {list.subList(j + 1, list.size()).clear(); // 删除多余的元素}}// 深度优先搜索 (DFS)boolean[] visited = new boolean[n + 1]; // 访问标记数组List<Integer> dfsOrder = new ArrayList<>(); // 存储 DFS 遍历结果Deque<Integer> stack = new ArrayDeque<>(); // 使用栈实现 DFSstack.push(1); // 从编号为 1 的文章开始while (!stack.isEmpty()) {int u = stack.pop(); // 弹出栈顶元素if (visited[u]) continue; // 如果已经访问过,跳过visited[u] = true; // 标记为已访问dfsOrder.add(u); // 记录访问顺序List<Integer> neighbors = adj.get(u); // 获取当前节点的邻居// 倒序遍历邻居,保证编号较小的优先被访问for (int i = neighbors.size() - 1; i >= 0; i--) {int v = neighbors.get(i);if (!visited[v]) {stack.push(v); // 将未访问的邻居压入栈}}}// 广度优先搜索 (BFS)visited = new boolean[n + 1]; // 重置访问标记数组List<Integer> bfsOrder = new ArrayList<>(); // 存储 BFS 遍历结果Queue<Integer> queue = new LinkedList<>(); // 使用队列实现 BFSqueue.offer(1); // 从编号为 1 的文章开始visited[1] = true; // 标记起点为已访问while (!queue.isEmpty()) {int u = queue.poll(); // 取出队首元素bfsOrder.add(u); // 记录访问顺序for (int v : adj.get(u)) { // 遍历当前节点的邻居if (!visited[v]) {visited[v] = true; // 标记为已访问queue.offer(v); // 将未访问的邻居加入队列}}}// 输出结果StringBuilder sb = new StringBuilder();for (int num : dfsOrder) {sb.append(num).append(' '); // 拼接 DFS 遍历结果}sb.append('\n');for (int num : bfsOrder) {sb.append(num).append(' '); // 拼接 BFS 遍历结果}System.out.print(sb); // 输出最终结果}
}
P3916 图的遍历
解题思路
图的表示:
- 使用邻接表表示有向图。由于题目要求找到从每个节点能到达的最大编号节点,因此可以构建图的反向边。
- 在反向图中,如果存在一条边
(u, v)
,则在图中加入边(v, u)
,这样我们可以从目标节点向源节点遍历。遍历策略:
- 为了计算每个节点的最大可达编号,我们可以采用深度优先搜索(DFS)。
- 从每个节点开始 DFS 时,如果当前节点已经被访问过,直接返回已存储的结果;否则,记录从该节点出发的最大可达编号。
- 为了确保每个节点的 DFS 都能得到准确的最大编号,可以从编号较大的节点开始 DFS。
结果存储:
- 使用一个数组 num 来存储每个节点的最大可达编号,数组的索引对应节点编号。
- 当遍历结束后,数组 num 中的每个元素即为所求结果。
import java.util.*;public class Main {static int MAXL = 100001; // 最大点数static int n, m; // 节点数和边数static int[] num = new int[MAXL]; // 记录从每个节点出发到达的最大节点编号static List<List<Integer>> g = new ArrayList<>(); // 邻接表public static void main(String[] args) {Scanner input = new Scanner(System.in);// 读取点数和边数n = input.nextInt();m = input.nextInt();// 初始化邻接表for (int i = 0; i <= n; i++) {g.add(new ArrayList<>()); // 为每个节点创建一个列表}// 反向建边for (int i = 0; i < m; i++) {// 边的起点int u = input.nextInt();// 边的终点int v = input.nextInt();// 邻接表添加反向边g.get(v).add(u);}// 从每个节点进行DFSfor (int i = n; i >= 1; i--) {dfs(i, i);}// 输出结果for (int i = 1; i <= n; i++) {System.out.print(num[i] + " ");}System.out.println();}private static void dfs(int x, int d) {if (num[x] != 0) return; // 如果访问过,则返回num[x] = d; // 记录从x出发到达的最大节点编号for (int neighbor : g.get(x)) {dfs(neighbor, d);}}
}
P1113 杂务
解题思路(拓扑排序)
1. 问题建模
- 每个杂务可以看作一个节点,依赖关系可以看作有向边。这是一个有向图的问题。
- 输入中每个任务的完成时间和依赖的任务形成了图的结构。
2. 使用拓扑排序
- 由于任务之间存在依赖关系,我们需要确保在计算一个任务的完成时间时,其所有依赖的任务都已经完成。
- 我们可以使用拓扑排序的方法来处理这个问题。入度(依赖的任务数量)为0的任务可以立即开始执行。
import java.util.*;public class Main {public static void main(String[] args) {Scanner input = new Scanner(System.in);int n = input.nextInt(); // 任务数量int[] index = new int[n + 1]; // 入度数组int[] time = new int[n + 1]; // 完成时间数组int[] lim = new int[n + 1]; // 每个任务的时间List<List<Integer>> edge = new ArrayList<>(n + 1); // 邻接表// 初始化邻接表for (int i = 0; i <= n; i++) {edge.add(new ArrayList<>());}// 读取任务信息for (int i = 1; i <= n; i++) {int x = input.nextInt(); // 任务编号lim[x] = input.nextInt(); // 任务所需时间while (true) {int y = input.nextInt(); // 读取依赖的任务if (y == 0) break; // 结束读取依赖edge.get(y).add(x); // 记录依赖关系index[x]++; // 更新入度}}// 队列用于拓扑排序Queue<Integer> q = new LinkedList<>();// 将入度为0的任务入队for (int i = 1; i <= n; i++) {if (index[i] == 0) {q.add(i);time[i] = lim[i]; // 初始化完成时间}}// 进行拓扑排序while (!q.isEmpty()) {int rhs = q.poll(); // 取出队首任务// 遍历依赖该任务的所有任务for (int u : edge.get(rhs)) {index[u]--; // 入度减1// 如果入度为0,则入队if (index[u] == 0) {q.add(u);}// 更新任务的完成时间time[u] = Math.max(time[u], time[rhs] + lim[u]);}}// 统计所有任务完成时间的最大值int ans = 0;for (int i = 1; i <= n; i++) {ans = Math.max(ans, time[i]);}// 输出结果System.out.println(ans);}
}
解题思路(动态规划)
我们可以使用动态规划的方法来计算每个任务的完成时间。每个任务的完成时间依赖于它所有依赖的任务的完成时间。
输入处理:
- 读取任务的数量
n
,并初始化完成时间数组ans
和最大完成时间maxAns
。- 逐个读取每个任务的编号、所需时间以及依赖的任务,直到读取到
0
为止。计算完成时间:
- 对于每个任务,首先记录其依赖任务的最大完成时间
tmp
。- 将当前任务的完成时间
ans[i]
设置为tmp
加上当前任务所需的时间。- 更新
maxAns
为当前任务的完成时间和已有的最大完成时间中的较大者。
import java.util.Scanner;public class Main {public static void main(String[] args) {Scanner input = new Scanner(System.in);int n = input.nextInt(); // 任务数量int[] ans = new int[10005]; // 完成时间数组int maxAns = 0; // 最大完成时间// 读取每个任务的信息for (int i = 1; i <= n; i++) {int taskId = input.nextInt(); // 任务编号int l = input.nextInt(); // 任务所需时间int tmp = 0; // 用于记录依赖任务的最大完成时间// 读取依赖项while (true) {int t = input.nextInt(); // 读取依赖的任务if (t == 0) break; // 结束读取依赖tmp = Math.max(ans[t], tmp); // 更新最大依赖完成时间}ans[taskId] = tmp + l; // 当前任务的完成时间maxAns = Math.max(ans[taskId], maxAns); // 更新最大完成时间}// 输出结果System.out.println(maxAns);}
}
P4017 最大食物链计数
解题思路
问题分析:题目要求计算从生产者(入度为0的节点)到顶级消费者(出度为0的节点)的所有路径数目之和。这类问题可以通过拓扑排序结合动态规划(DP)来解决。
图构建:使用邻接表存储每个节点的后继节点(即该节点被哪些生物捕食),并统计每个节点的入度和出度。
拓扑排序:初始化所有生产者节点的路径数为1,然后按照拓扑顺序处理每个节点,将其路径数累加到其后继节点的路径数中。
动态规划:每个节点的路径数表示以该节点为终点的路径数目。在拓扑排序过程中,逐步更新每个节点的路径数。
结果计算:遍历所有出度为0的节点(顶级消费者),将其路径数累加得到最终结果,并取模处理。
import java.io.*;
import java.util.*;public class Main {static final int MOD = 80112002; // 定义取模常量public static void main(String[] args) throws IOException {BufferedReader br = new BufferedReader(new InputStreamReader(System.in));String[] parts = br.readLine().split(" ");int n = Integer.parseInt(parts[0]); // 节点数int m = Integer.parseInt(parts[1]); // 边数// 邻接表表示图List<Integer>[] adj = new ArrayList[n + 1];for (int i = 1; i <= n; i++) {adj[i] = new ArrayList<>();}int[] inDegree = new int[n + 1]; // 入度数组int[] outDegree = new int[n + 1]; // 出度数组// 读取边信息并构建图for (int i = 0; i < m; i++) {parts = br.readLine().split(" ");int a = Integer.parseInt(parts[0]); // 起点int b = Integer.parseInt(parts[1]); // 终点adj[a].add(b); // 添加边 a -> binDegree[b]++; // 增加 b 的入度outDegree[a]++; // 增加 a 的出度}int[] dp = new int[n + 1]; // dp[i] 表示从某个生产者到节点 i 的路径数Queue<Integer> queue = new LinkedList<>(); // 队列用于拓扑排序// 初始化生产者(入度为 0 的节点)for (int i = 1; i <= n; i++) {if (inDegree[i] == 0) {dp[i] = 1; // 生产者的路径数初始化为 1queue.offer(i); // 将生产者加入队列}}// 拓扑排序处理while (!queue.isEmpty()) {int u = queue.poll(); // 取出队首节点for (int v : adj[u]) { // 遍历 u 的所有邻接节点dp[v] = (dp[v] + dp[u]) % MOD; // 更新路径数,取模防止溢出if (--inDegree[v] == 0) { // 如果 v 的入度变为 0,加入队列queue.offer(v);}}}// 计算所有顶级消费者的路径总和int sum = 0;for (int i = 1; i <= n; i++) {if (outDegree[i] == 0) { // 顶级消费者(出度为 0 的节点)sum = (sum + dp[i]) % MOD; // 累加路径数,取模防止溢出}}System.out.print(sum); // 输出结果}
}
P1807 最长路
解题思路
问题分析:我们需要求解从顶点
1
到顶点n
的最长路径。由于这是一个有向无环图(DAG),可以使用拓扑排序来简化求解最长路径的问题。拓扑排序与动态规划:
- 首先,构建图的邻接表并计算每个节点的入度。
- 使用拓扑排序从起点开始遍历图,确保每个节点只会在其前驱节点都处理完后才被处理,从而避免环。
- 利用动态规划,设
dist[i]
表示从起点1
到节点i
的最长路径。初始化dist[1] = 0
,其他节点的dist
初始化为负无穷。- 在遍历每条边
u -> v
时,若dist[u] + w > dist[v]
,则更新dist[v] = dist[u] + w
。结果输出:
- 最后,若
dist[n]
仍为负无穷,表示1
无法到达n
,输出-1
。- 否则,输出
dist[n]
。
import java.util.*;public class Main {static final int INF = Integer.MIN_VALUE;static int[] dist, inDegree;static List<int[]>[] graph;static int n, m;public static void main(String[] args) {Scanner input = new Scanner(System.in);n = input.nextInt();m = input.nextInt();dist = new int[n + 1];Arrays.fill(dist, INF); // 初始化距离dist[1] = 0; // 起点的距离为0inDegree = new int[n + 1];graph = new ArrayList[n + 1];for (int i = 1; i <= n; i++) {graph[i] = new ArrayList<>();}for (int i = 0; i < m; i++) {int u = input.nextInt();int v = input.nextInt();int w = input.nextInt();graph[u].add(new int[]{v, w});inDegree[v]++;}if (topoSortAndLongestPath()) {System.out.println(dist[n]);} else {System.out.println(-1); // 无法到达n}}static boolean topoSortAndLongestPath() {Queue<Integer> queue = new LinkedList<>();// 初始化拓扑排序队列for (int i = 1; i <= n; i++) {if (inDegree[i] == 0) {queue.offer(i);}}int visitedCount = 0;while (!queue.isEmpty()) {int u = queue.poll();visitedCount++;for (int[] edge : graph[u]) {int v = edge[0], w = edge[1];// 更新最长路径if (dist[u] != INF) {dist[v] = Math.max(dist[v], dist[u] + w);}// 更新入度并检查是否可以入队if (--inDegree[v] == 0) {queue.offer(v);}}}return visitedCount == n && dist[n] != INF;}
}
P1127 词链
解题思路
输入与初始化:
- 首先读取字符串数量
n
和每个字符串。利用ind
数组记录每个字符作为字符串结束字符的数量,利用rnd
数组记录每个字符作为字符串开始字符的数量。邻接表构建:
- 对字符串进行字典序排序。通过两层循环检查每个字符串对,如果一个字符串的最后一个字符与另一个字符串的第一个字符相同,则在邻接表中记录这一边。
欧拉路径搜索:
- 欧拉路径要求图的入度和出度特定匹配。通常,起点的出度应大于入度一,而其他节点的入度与出度应相等。因此,使用
ind
和rnd
数组来找到合适的起点。- 使用 DFS 方法搜索所有可能的路径。如果找到了包含所有字符串的路径,则输出结果并结束程序。
处理边界条件:
- 如果没有找到任何有效路径,程序输出 "***" 表示无法形成有效的欧拉路径。
import java.util.*;public class Main {static int n; // 字符串数量static String[] a = new String[1001]; // 存储输入的字符串static List<Integer>[] e = new ArrayList[1001]; // 邻接表,用于存储边static int[] ind = new int[1001]; // 入度数组,记录每个字符作为结束字符的字符串数量static int[] outd = new int[1001]; // 出度数组,记录每个字符作为开始字符的字符串数量static boolean[] used = new boolean[1001]; // 标记字符串是否已经使用// 深度优先搜索函数static void dfs(int s, String curr, int count) {// 当找到一个完整的路径时if (count == n) {System.out.println(curr.substring(0, curr.length() - 1)); // 去掉最后的点System.exit(0); // 找到答案后退出程序}// 遍历当前字符串可以连接的下一个字符串for (int next : e[s]) {if (!used[next]) { // 如果下一个字符串未被使用used[next] = true; // 标记为已使用// 递归调用,继续深度优先搜索dfs(next, curr + a[next] + ".", count + 1);used[next] = false; // 回溯,取消使用标记}}}public static void main(String[] args) {Scanner input = new Scanner(System.in);n = input.nextInt(); // 读取字符串数量// 读取字符串并初始化数据结构for (int i = 1; i <= n; ++i) {a[i] = input.next(); // 存储字符串ind[a[i].charAt(0)]++; // 更新入度outd[a[i].charAt(a[i].length() - 1)]++; // 更新出度e[i] = new ArrayList<>(); // 初始化邻接表}Arrays.sort(a, 1, n + 1); // 按字典序排序字符串// 建立邻接关系for (int i = 1; i <= n; ++i) {for (int j = 1; j <= n; ++j) {// 如果当前字符串的最后一个字符与下一个字符串的第一个字符相同if (i != j && a[i].charAt(a[i].length() - 1) == a[j].charAt(0)) {e[i].add(j); // 记录边}}}// 尝试以每个合适的字符串作为起始点for (int i = 1; i <= n; ++i) {if (ind[a[i].charAt(0)] == outd[a[i].charAt(0)] + 1) {used[i] = true; // 标记当前字符串为已使用dfs(i, a[i] + ".", 1); // 从当前字符串开始 DFSused[i] = false; // 回溯}}// 如果没有找到合适的起始点,默认从第一个字符串开始used[1] = true;dfs(1, a[1] + ".", 1); // 从第一个字符串开始 DFSused[1] = false; // 回溯// 如果没有找到路径,输出 "***"System.out.println("***");}
}
P2853 [USACO06DEC] Cow Picnic S
解题思路
构建图:
- 使用邻接表表示有向图,其中每个牧场指向可以到达的其他牧场。
- 反向图也很重要,用于检查某个牧场是否可以被所有奶牛到达。
BFS 遍历:
- 对每头奶牛使用 BFS 遍历,从奶牛所在的牧场出发,找出所有可达的牧场,记录这些牧场。
- 使用一个集合来存储所有可达的牧场。
反向 BFS 检查:
- 对于每个在可达集合中的牧场,使用反向 BFS 来检查从该牧场能否到达所有奶牛的牧场。
- 如果从当前牧场出发,可以访问到所有奶牛的起始牧场,则该牧场是可供聚餐的地点。
结果统计:
- 统计所有能被所有奶牛到达的牧场数量。
import java.util.*;public class Main {public static void main(String[] args) {Scanner input = new Scanner(System.in);// 读取输入值int K = input.nextInt(); // 奶牛数量int N = input.nextInt(); // 牧场数量int M = input.nextInt(); // 路径数量int[] cowPositions = new int[K]; // 存储每头奶牛所在的牧场for (int i = 0; i < K; i++) {cowPositions[i] = input.nextInt();}List<List<Integer>> graph = new ArrayList<>(); // 牧场的有向图List<List<Integer>> reverseGraph = new ArrayList<>(); // 牧场的反向图for (int i = 0; i <= N; i++) {graph.add(new ArrayList<>());reverseGraph.add(new ArrayList<>());}// 读取有向路径并构建图for (int i = 0; i < M; i++) {int A = input.nextInt();int B = input.nextInt();graph.get(A).add(B); // A 到 B 的有向边reverseGraph.get(B).add(A); // 反向边}// 步骤 1:找出所有奶牛可以到达的牧场Set<Integer> reachableFromCows = new HashSet<>(); // 可达的牧场集合boolean[] visited = new boolean[N + 1]; // 访问标记// 对每头奶牛进行 BFS,找到可达的牧场for (int cow : cowPositions) {if (!visited[cow]) {bfs(cow, graph, visited, reachableFromCows);}}// 步骤 2:检查哪些可达牧场能够被所有奶牛到达int count = 0; // 可供进食的牧场计数for (int pasture : reachableFromCows) {visited = new boolean[N + 1]; // 重置访问标记int reachableCount = 0; // 记录可以到达的奶牛数量// 反向 BFS,检查当前牧场是否可以到达所有奶牛的牧场bfsReverse(pasture, reverseGraph, visited);// 统计可以到达的奶牛数量for (int cow : cowPositions) {if (visited[cow]) {reachableCount++;}}// 如果当前牧场可以到达所有奶牛,计数加一if (reachableCount == K) {count++;}}// 输出结果System.out.println(count);}// BFS 方法,找出从起始牧场可达的所有牧场private static void bfs(int start, List<List<Integer>> graph, boolean[] visited, Set<Integer> reachable) {Queue<Integer> queue = new LinkedList<>();queue.add(start);visited[start] = true;while (!queue.isEmpty()) {int current = queue.poll(); // 取出队首reachable.add(current); // 记录可达牧场// 遍历相邻牧场for (int neighbor : graph.get(current)) {if (!visited[neighbor]) {visited[neighbor] = true; // 标记为已访问queue.add(neighbor); // 入队}}}}// 反向 BFS 方法,找出能到达当前牧场的奶牛数量private static void bfsReverse(int start, List<List<Integer>> reverseGraph, boolean[] visited) {Queue<Integer> queue = new LinkedList<>();queue.add(start);visited[start] = true;while (!queue.isEmpty()) {int current = queue.poll(); // 取出队首// 遍历反向相邻牧场for (int neighbor : reverseGraph.get(current)) {if (!visited[neighbor]) {visited[neighbor] = true; // 标记为已访问queue.add(neighbor); // 入队}}}}
}
P1363 幻象迷宫
解题思路
解题代码修改自https://www.luogu.com.cn/record/206017952。
1. 输入处理
- 迷宫大小:通过输入读取迷宫的行数 n 和列数 m。
- 迷宫地图:读取迷宫的每一行数据,存储到二维字符数组 s 中。
- 起点查找:遍历迷宫,找到起点
'S'
的位置(startX, startY)
。
2. 广度优先搜索 (BFS)
- 队列初始化:使用队列
Queue<Point>
存储当前访问的点,起点首先入队。- 状态标记:使用二维数组 book 记录每个位置的访问状态,状态通过自定义哈希函数 f(x, y) 计算。
- 方向扩展:定义四个方向的移动向量 ne,分别表示右、下、左、上。
BFS 的核心逻辑:
- 从队列中取出当前点
(x, y)
。- 遍历四个方向,计算新位置
(nx, ny)
。- 无限地图映射:将无限地图的坐标
(nx, ny)
映射到有限范围(tx, ty)
。- 状态判断:
- 如果新位置是墙壁
'#'
,跳过。- 如果新位置未被访问过,标记状态并加入队列。
- 如果新位置已被访问过,但状态不同,说明存在循环路径,返回
true
。- 如果队列为空且未发现循环路径,返回
false
。
3. 自定义哈希函数
- 函数 f(x, y) 用于计算每个位置的状态值,确保在无限地图中不同的坐标映射到有限范围时仍能区分状态。
- 公式:
((x + n * 4) / n) * 131 + ((y + m * 4) / m) * 13
(x + n * 4) / n
和(y + m * 4) / m
确保坐标映射到有限范围。- 乘以不同的常数(131 和 13)避免哈希冲突。
4. 无限地图映射
- 迷宫是无限重复的,通过以下公式将无限坐标
(nx, ny)
映射到有限范围(tx, ty)
:tx = (nx + n * 10) % n;
ty = (ny + m * 10) % m;
+ n * 10
和+ m * 10
确保坐标为正数。% n
和% m
将坐标限制在迷宫的行列范围内。
import java.util.*;public class Main {// 定义四个方向的移动向量:右、下、左、上static int[][] ne = {{0,1}, {1,0}, {0,-1}, {-1,0}};static int[][] book; // 记录每个位置的状态static char[][] s; // 存储迷宫地图static int n, m; // 迷宫的行数和列数// 定义一个点的类,用于存储坐标static class Point {int x, y;Point(int x, int y) {this.x = x;this.y = y;}}// 自定义哈希函数,用于标记每个位置的状态static int f(int x, int y) {return ((x + n * 4) / n) * 131 + ((y + m * 4) / m) * 13;}// 广度优先搜索 (BFS) 判断是否存在循环路径static boolean bfs(int x, int y) {Queue<Point> q = new LinkedList<>(); // 队列用于存储当前访问的点q.offer(new Point(x, y)); // 将起点加入队列book[x][y] = f(x, y); // 标记起点的状态while (!q.isEmpty()) {Point u = q.poll(); // 取出队首元素for (int[] dir : ne) { // 遍历四个方向int nx = u.x + dir[0]; // 新的 x 坐标int ny = u.y + dir[1]; // 新的 y 坐标// 处理无限地图的坐标映射int tx = (nx + n * 10) % n; // 映射到迷宫内的行int ty = (ny + m * 10) % m; // 映射到迷宫内的列int t = f(nx, ny); // 计算新的状态// 如果当前位置是墙壁,跳过if (s[tx][ty] == '#') continue;// 如果当前位置未被访问过if (book[tx][ty] == 0) {book[tx][ty] = t; // 标记状态q.offer(new Point(nx, ny)); // 加入队列}// 如果当前位置被访问过,但状态不同,说明存在循环else if (book[tx][ty] != t) {return true;}}}return false; // 如果搜索结束没有发现循环,返回 false}public static void main(String[] args) {Scanner scanner = new Scanner(System.in);while (scanner.hasNextLine()) {String temp = scanner.nextLine().trim(); // 读取迷宫的大小if (temp.isEmpty()) break; // 输入结束条件n = Integer.parseInt(temp.split(" ")[0]); // 行数m = Integer.parseInt(temp.split(" ")[1]); // 列数s = new char[n][m]; // 初始化迷宫地图book = new int[n][m]; // 初始化标记数组// 读取地图数据for (int i = 0; i < n; i++) {String l = scanner.nextLine().trim();while (l.isEmpty()) { // 跳过空行l = scanner.nextLine().trim();}s[i] = l.toCharArray(); // 将每行数据存入地图}// 查找起点 'S'int startX = 0, startY = 0;outerloop:for (int i = 0; i < n; i++) {for (int j = 0; j < m; j++) {if (s[i][j] == 'S') { // 找到起点startX = i;startY = j;break outerloop; // 跳出双重循环}}}// 输出结果:是否存在循环路径System.out.println(bfs(startX, startY) ? "Yes" : "No");}scanner.close();}
}
P1347 排序
解题思路
输入处理:
- 首先读取节点数量
n
和关系数量m
。- 根据输入的边(关系),更新邻接表和入度数组,建立图的结构。
拓扑排序逻辑:
- 通过
topo(int r)
函数进行拓扑排序。该函数尝试构造一个拓扑排序的结果。- 维护一个临时入度数组
t
,并将入度为0的节点入栈(代表可以先访问的节点)。- 在处理每个节点时,如果发现有多个节点可以入栈(即有多个入度为0的节点),则标记
finished
为false
,表示当前状态下可能有多种排序方式。- 每当处理完一个节点后,更新其相邻节点的入度,并检查这些相邻节点是否可以入栈。
- 如果最终排序结果中的节点数量少于总节点数量,表示图中存在环,返回
false
。判断和输出结果:
- 在每次添加新关系后,都执行一次拓扑排序,如果发现不一致(如存在环),则输出错误信息并终止。
- 如果成功完成排序且找到了有效的排序顺序,则打印排序结果。
- 如果在处理完所有关系后仍未找到有效排序,则输出“无法确定排序顺序”。
import java.util.*;public class Main {static final int MAXN = 30; // 最大节点数static int n, m; // n 为节点数量,m 为关系数量static List<Integer>[] e = new ArrayList[MAXN]; // 邻接表static int[] degree = new int[MAXN]; // 入度数组static int[] a = new int[MAXN]; // 排序结果static Stack<Integer> s = new Stack<>(); // 用于拓扑排序的栈static boolean[] vis = new boolean[MAXN]; // 访问标记static int mrk = 0; // 标记是否成功排序// 拓扑排序函数,返回值为真表示成功static boolean topo(int r) {int sz = 0; // 记录排序结果的大小boolean finished = true; // 标记是否存在多个入度为0的节点int[] t = new int[MAXN]; // 用于临时存储入度// 初始化入度和入度为0的节点for (int i = 0; i < n; i++) {t[i] = degree[i];if (t[i] == 0) {s.push(i);vis[i] = true; // 标记为已访问}}while (!s.isEmpty()) {if (s.size() > 1) finished = false; // 存在多个入度为0的节点int k = s.pop(); // 取出栈顶元素a[sz++] = k; // 记录排序结果// 更新邻接节点的入度for (int v : e[k]) {t[v]--;}// 查找新的入度为0的节点for (int i = 0; i < n; i++) {if (t[i] == 0 && !vis[i]) {s.push(i);vis[i] = true; // 标记为已访问}}}if (sz < n) return false; // 如果排序结果小于节点数,表示有循环if (finished && mrk == 0) mrk = r; // 更新成功标记return true;}public static void main(String[] args) {Scanner input = new Scanner(System.in);n = input.nextInt(); // 输入节点数量m = input.nextInt(); // 输入关系数量// 初始化邻接表for (int i = 0; i < MAXN; i++) {e[i] = new ArrayList<>();}// 输入边并构建图for (int i = 1; i <= m; i++) {String c = input.next(); // 读取边int x = c.charAt(0) - 'A'; // 起点int y = c.charAt(2) - 'A'; // 终点e[x].add(y); // 添加边degree[y]++; // 更新入度if (mrk > 0) {break; // 如果已经成功排序,跳过后续输入(记录成环的特殊情况)}// 执行拓扑排序if (!topo(i)) {System.out.println("Inconsistency found after " + i + " relations.");return; // 如果存在矛盾,终止程序}Arrays.fill(vis, false); // 重置访问标记}// 输出排序结果if (mrk > 0) {System.out.print("Sorted sequence determined after " + mrk + " relations: ");for (int j = 0; j < n; j++) {System.out.print((char)(a[j] + 'A')); // 输出排序的字母}System.out.println(".");} else {System.out.println("Sorted sequence cannot be determined.");}}
}
P1983 [NOIP2013 普及组] 车站分级
解题思路
图的表示:
- 使用
Node
类表示图中的每个节点。每个节点包含:
level
: 表示该节点的层级,初始值为 1。in_d
: 表示该节点的入度(指向该节点的边的数量)。adj
: 使用BitSet
来存储该节点的邻接节点(即哪些节点指向它)。输入处理:
- 使用
StreamTokenizer
进行高效的输入处理,以便快速读取大量数据。- 首先读取节点数
N
,然后读取边的数目M
。构建图:
- 对于每条边,首先记录每个相关节点的入度。
- 遍历节点范围,将未被访问的节点标记,并建立它们与当前边的邻接关系。
拓扑排序:
- 使用队列(
ArrayDeque
)来存储入度为 0 的节点。- 在遍历队列时,处理当前节点的所有邻接节点,减少它们的入度。如果某个邻接节点的入度变为 0,则将其加入队列。
- 同时更新每个邻接节点的层级,确保每个节点的层级总是反映其在图中的位置。
计算最大层级:
- 遍历所有节点,找到最大层级值,输出结果。
import java.io.*;
import java.util.ArrayDeque;
import java.util.Arrays;
import java.util.BitSet;public class Main {// 定义节点类,保存每个节点的深度和入度private static class Node {short level; // 当前节点的层级short in_d = 0; // 该节点的入度BitSet adj; // 邻接表,用 BitSet 存储连接的节点Node() {level = 1; // 默认层级为 1}}public static void main(String[] args) throws IOException {// 使用 StreamTokenizer 进行高效的输入处理StreamTokenizer in = new StreamTokenizer(new BufferedReader(new InputStreamReader(System.in)));// 读取节点数量 Nin.nextToken();int N = (int) in.nval;// 创建节点数组Node[] nodes = new Node[N + 1];boolean[] vis = new boolean[N + 1]; // 记录是否访问过short[] st = new short[N + 1]; // 存储临时数据// 初始化节点for (int i = 1; i < N + 1; i++) nodes[i] = new Node();// 读取边的数量 Min.nextToken();int M = (int) in.nval;// 处理每条边for (int i = 0; i < M; i++) {in.nextToken(); // 读取当前边的节点数st[0] = (short) in.nval; // st[0] 存储当前边的节点数// 读取当前边的所有节点for (int j = 1; j <= st[0]; j++) {in.nextToken();st[j] = (short) in.nval; // 将节点存入 st 数组vis[st[j]] = true; // 标记节点为已访问}// 遍历节点范围,设置入度和邻接关系for (int j = st[1]; j <= st[st[0]]; j++) {if (vis[j]) continue; // 如果节点已经访问过,跳过// 初始化邻接表if (nodes[j].adj == null) nodes[j].adj = new BitSet();// 更新入度和邻接关系for (int k = 1; k <= st[0]; k++) {if (!nodes[j].adj.get(st[k])) { // 如果未连接nodes[st[k]].in_d++; // 增加入度nodes[j].adj.set(st[k]); // 记录邻接关系}}}Arrays.fill(vis, false); // 清空访问标记}// 使用队列进行拓扑排序ArrayDeque<Node> deque = new ArrayDeque<>();// 将入度为 0 的节点加入队列for (int i = 1; i <= N; i++) if (nodes[i].in_d == 0) deque.add(nodes[i]);// 拓扑排序过程while (!deque.isEmpty()) {Node cur = deque.poll(); // 取出队首节点if (cur.adj == null) continue; // 如果没有邻接节点,跳过// 遍历当前节点的所有邻接节点int i = cur.adj.nextSetBit(0);while (i >= 0) {// 减少邻接节点的入度if (--nodes[i].in_d == 0) {deque.add(nodes[i]); // 如果入度为 0,则加入队列// 更新邻接节点的层级if (cur.level + 1 > nodes[i].level) nodes[i].level = (short) (cur.level + 1);}i = cur.adj.nextSetBit(i + 1); // 获取下一个邻接节点}}// 计算最大层级int ans = 0;for (int i = 1; i <= N; i++) ans = Math.max(ans, nodes[i].level);// 输出结果System.out.println(ans);}
}