class061 最小生成樹【算法】
2023-12-8 11:48:12
算法講解061【必備】最小生成樹
code1 P3366 【模板】最小生成樹
// Kruskal算法模版(洛谷)
// 靜態空間實現
// 測試鏈接 : https://www.luogu.com.cn/problem/P3366
// 請同學們務必參考如下代碼中關于輸入、輸出的處理
// 這是輸入輸出處理效率很高的寫法
// 提交以下所有代碼,把主類名改成Main,可以直接通過
package class061;// Kruskal算法模版(洛谷)
// 靜態空間實現
// 測試鏈接 : https://www.luogu.com.cn/problem/P3366
// 請同學們務必參考如下代碼中關于輸入、輸出的處理
// 這是輸入輸出處理效率很高的寫法
// 提交以下所有代碼,把主類名改成Main,可以直接通過
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.io.StreamTokenizer;
import java.util.Arrays;// 時間復雜度O(m * log m) + O(n + m)
public class Code01_Kruskal {public static int MAXN = 5001;public static int MAXM = 200001;public static int[] father = new int[MAXN];// u, v, wpublic static int[][] edges = new int[MAXM][3];public static int n, m;public static void build() {for (int i = 1; i <= n; i++) {father[i] = i;}}public static int find(int i) {if (i != father[i]) {father[i] = find(father[i]);}return father[i];}// 如果x和y本來就是一個集合,返回false// 如果x和y不是一個集合,合并之后返回truepublic static boolean union(int x, int y) {int fx = find(x);int fy = find(y);if (fx != fy) {father[fx] = fy;return true;} else {return false;}}public static void main(String[] args) throws IOException {BufferedReader br = new BufferedReader(new InputStreamReader(System.in));StreamTokenizer in = new StreamTokenizer(br);PrintWriter out = new PrintWriter(new OutputStreamWriter(System.out));while (in.nextToken() != StreamTokenizer.TT_EOF) {n = (int) in.nval;in.nextToken();m = (int) in.nval;build();for (int i = 0; i < m; i++) {in.nextToken();edges[i][0] = (int) in.nval;in.nextToken();edges[i][1] = (int) in.nval;in.nextToken();edges[i][2] = (int) in.nval;}Arrays.sort(edges, 0, m, (a, b) -> a[2] - b[2]);int ans = 0;int edgeCnt = 0;for (int[] edge : edges) {if (union(edge[0], edge[1])) {edgeCnt++;ans += edge[2];}}out.println(edgeCnt == n - 1 ? ans : "orz");}out.flush();out.close();br.close();}}
code2 P3366 【模板】最小生成樹
// Prim算法模版(洛谷)
// 動態空間實現
// 測試鏈接 : https://www.luogu.com.cn/problem/P3366
// 請同學們務必參考如下代碼中關于輸入、輸出的處理
// 這是輸入輸出處理效率很高的寫法
// 提交以下所有代碼,把主類名改成Main,可以直接通過
package class061;// Prim算法模版(洛谷)
// 動態空間實現
// 測試鏈接 : https://www.luogu.com.cn/problem/P3366
// 請同學們務必參考如下代碼中關于輸入、輸出的處理
// 這是輸入輸出處理效率很高的寫法
// 提交以下所有代碼,把主類名改成Main,可以直接通過import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.io.StreamTokenizer;
import java.util.ArrayList;
import java.util.PriorityQueue;// 時間復雜度O(n + m) + O(m * log m)
public class Code02_PrimDynamic {public static void main(String[] args) throws IOException {BufferedReader br = new BufferedReader(new InputStreamReader(System.in));StreamTokenizer in = new StreamTokenizer(br);PrintWriter out = new PrintWriter(new OutputStreamWriter(System.out));while (in.nextToken() != StreamTokenizer.TT_EOF) {ArrayList<ArrayList<int[]>> graph = new ArrayList<>();int n = (int) in.nval;for (int i = 0; i <= n; i++) {graph.add(new ArrayList<>());}in.nextToken();int m = (int) in.nval;for (int i = 0, u, v, w; i < m; i++) {in.nextToken();u = (int) in.nval;in.nextToken();v = (int) in.nval;in.nextToken();w = (int) in.nval;graph.get(u).add(new int[] { v, w });graph.get(v).add(new int[] { u, w });}// int[] record// record[0] : 到達的節點// record[1] : 到達的花費PriorityQueue<int[]> heap = new PriorityQueue<>((a, b) -> a[1] - b[1]);for (int[] edge : graph.get(1)) {heap.add(edge);}// 哪些節點已經發現過了boolean[] set = new boolean[n + 1];int nodeCnt = 1;set[1] = true;int ans = 0;while (!heap.isEmpty()) {int[] edge = heap.poll();int next = edge[0];int cost = edge[1];if (!set[next]) {nodeCnt++;set[next] = true;ans += cost;for (int[] e : graph.get(next)) {heap.add(e);}}}out.println(nodeCnt == n ? ans : "orz");}out.flush();out.close();br.close();}}
code2 P3366 【模板】最小生成樹
// 建圖用鏈式前向星
// 堆也是用數組結構手寫的、且只和節點個數有關
// 這個實現留給有需要的同學
// 但是一般情況下并不需要做到這個程度
package class061;// Prim算法優化(洛谷)
// 靜態空間實現
// 時間復雜度O(n + m) + O((m+n) * log n)
// 測試鏈接 : https://www.luogu.com.cn/problem/P3366
// 請同學們務必參考如下代碼中關于輸入、輸出的處理
// 這是輸入輸出處理效率很高的寫法
// 提交以下所有代碼,把主類名改成Main,可以直接通過import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.io.StreamTokenizer;
import java.util.Arrays;// 建圖用鏈式前向星
// 堆也是用數組結構手寫的、且只和節點個數有關
// 這個實現留給有需要的同學
// 但是一般情況下并不需要做到這個程度public class Code02_PrimStatic {public static int MAXN = 5001;public static int MAXM = 400001;public static int n, m;// 鏈式前向星建圖public static int[] head = new int[MAXN];public static int[] next = new int[MAXM];public static int[] to = new int[MAXM];public static int[] weight = new int[MAXM];public static int cnt;// 改寫的堆結構public static int[][] heap = new int[MAXN][2];// where[v] = -1,表示v這個節點,從來沒有進入過堆// where[v] = -2,表示v這個節點,已經彈出過了// where[v] = i(>=0),表示v這個節點,在堆上的i位置public static int[] where = new int[MAXN];// 堆的大小public static int heapSize;// 找到的節點個數public static int nodeCnt;public static void build() {cnt = 1;heapSize = 0;nodeCnt = 0;Arrays.fill(head, 1, n + 1, 0);Arrays.fill(where, 1, n + 1, -1);}public static void addEdge(int u, int v, int w) {next[cnt] = head[u];to[cnt] = v;weight[cnt] = w;head[u] = cnt++;}// 當前處理的是編號為ei的邊!public static void addOrUpdateOrIgnore(int ei) {int v = to[ei];int w = weight[ei];// 去往v點,權重wif (where[v] == -1) {// v這個點,從來沒有進入過堆!heap[heapSize][0] = v;heap[heapSize][1] = w;where[v] = heapSize++;heapInsert(where[v]);} else if (where[v] >= 0) {// v這個點的記錄,在堆上的位置是where[v]heap[where[v]][1] = Math.min(heap[where[v]][1], w);heapInsert(where[v]);}}public static void heapInsert(int i) {while (heap[i][1] < heap[(i - 1) / 2][1]) {swap(i, (i - 1) / 2);i = (i - 1) / 2;}}public static int u;public static int w;// 堆頂的記錄:節點 -> u、到節點的花費 -> wpublic static void pop() {u = heap[0][0];w = heap[0][1];swap(0, --heapSize);heapify(0);where[u] = -2;nodeCnt++;}public static void heapify(int i) {int l = 1;while (l < heapSize) {int best = l + 1 < heapSize && heap[l + 1][1] < heap[l][1] ? l + 1 : l;best = heap[best][1] < heap[i][1] ? best : i;if (best == i) {break;}swap(best, i);i = best;l = i * 2 + 1;}}public static boolean isEmpty() {return heapSize == 0;}// 堆上,i位置的信息 和 j位置的信息 交換!public static void swap(int i, int j) {int a = heap[i][0];int b = heap[j][0];where[a] = j;where[b] = i;int[] tmp = heap[i];heap[i] = heap[j];heap[j] = tmp;}public static void main(String[] args) throws IOException {BufferedReader br = new BufferedReader(new InputStreamReader(System.in));StreamTokenizer in = new StreamTokenizer(br);PrintWriter out = new PrintWriter(new OutputStreamWriter(System.out));while (in.nextToken() != StreamTokenizer.TT_EOF) {n = (int) in.nval;in.nextToken();m = (int) in.nval;build();for (int i = 0, u, v, w; i < m; i++) {in.nextToken();u = (int) in.nval;in.nextToken();v = (int) in.nval;in.nextToken();w = (int) in.nval;addEdge(u, v, w);addEdge(v, u, w);}int ans = prim();out.println(nodeCnt == n ? ans : "orz");}out.flush();out.close();br.close();}public static int prim() {// 1節點出發nodeCnt = 1;where[1] = -2;for (int ei = head[1]; ei > 0; ei = next[ei]) {addOrUpdateOrIgnore(ei);}int ans = 0;while (!isEmpty()) {pop();ans += w;for (int ei = head[u]; ei > 0; ei = next[ei]) {addOrUpdateOrIgnore(ei);}}return ans;}}
code3 1168.水資源分配優化
// 水資源分配優化
// 村里面一共有 n 棟房子。我們希望通過建造水井和鋪設管道來為所有房子供水。
// 對于每個房子 i,我們有兩種可選的供水方案:一種是直接在房子內建造水井
// 成本為 wells[i - 1] (注意 -1 ,因為 索引從0開始 )
// 另一種是從另一口井鋪設管道引水,數組 pipes 給出了在房子間鋪設管道的成本,
// 其中每個 pipes[j] = [house1j, house2j, costj]
// 代表用管道將 house1j 和 house2j連接在一起的成本。連接是雙向的。
// 請返回 為所有房子都供水的最低總成本
// 測試鏈接 : https://leetcode.cn/problems/optimize-water-distribution-in-a-village/
題目:
1168水資源分配優化Plus
困難
村里面一共有n
棟房子。我們希望通過建造水和鋪設管道來為所有房子供水。
對于每個房子i
,我們有兩種可選的供水方案:
一種是直接在房子內建造水井,成本為 wells[i - 1]
(注意-1
,因為索引從0開始);
另一種是從另一口井鋪設管道引水,數組pipes
給出了在房子間鋪設管道的成本,其中每個 pipes[j] = [house1j,house2j,costj]
代表用管道
將house1j
和house2j
連接在一起的成本。連接是雙向的。
請返回為所有房子都供水的最低總成本
假定有一個水源連接著所有房子,對應邊的權重就是wells,
在采用最小生成樹算法,把連接水源的權值最小求出來。
示例一
輸入:n =3,wells = [1,2,2],pipes =[[1,2,1],[2,3,1l]
輸出:3
解釋:
上圖展示了鋪設管道連接房屋的成本最好的策略是在第一個房子里建造水井(成本為 1),
然后將其他房子鋪設管道連起來(成本為 2),所以總成本為3示例 2:
輸入:n = 2,wells = [1,1],pipes =[[1,2,11]
輸出:2
解釋:我們可以用以下三種方法中的一種來提供低成本的水:
選項1:
在1號房子里面建一口井,成本為1
在房子2內建造井,成本為1
總成本是2。
選項2:
在1號房子里面建一口井,成本為1
花費1連接房子2和房子1。
總成本是2。
選項3:
在房子2內建造井,成本為1
花費1連接房子1和房子2
總成本是2。
注意,我們可以用cost 1或cost 2連接房子1和房子2
但我們總是選擇最便宜的選項。
提示:
- 2<=n<= 104
wells.length == n
- 0<= wells[i] <= 105
- 1<= pipes.length <= 104
pipes[j].length == 3
- 1 <= house1j , house2j <= n
- 0<= costj <= 105
- house1j != house2j
package class061;import java.util.Arrays;// 水資源分配優化
// 村里面一共有 n 棟房子。我們希望通過建造水井和鋪設管道來為所有房子供水。
// 對于每個房子 i,我們有兩種可選的供水方案:一種是直接在房子內建造水井
// 成本為 wells[i - 1] (注意 -1 ,因為 索引從0開始 )
// 另一種是從另一口井鋪設管道引水,數組 pipes 給出了在房子間鋪設管道的成本,
// 其中每個 pipes[j] = [house1j, house2j, costj]
// 代表用管道將 house1j 和 house2j連接在一起的成本。連接是雙向的。
// 請返回 為所有房子都供水的最低總成本
// 測試鏈接 : https://leetcode.cn/problems/optimize-water-distribution-in-a-village/
public class Code03_OptimizeWaterDistribution {public static int minCostToSupplyWater(int n, int[] wells, int[][] pipes) {build(n);for (int i = 0; i < n; i++, cnt++) {// wells : 100 30// 0(1) 1(2)edges[cnt][0] = 0;edges[cnt][1] = i + 1;edges[cnt][2] = wells[i];}for (int i = 0; i < pipes.length; i++, cnt++) {edges[cnt][0] = pipes[i][0];edges[cnt][1] = pipes[i][1];edges[cnt][2] = pipes[i][2];}Arrays.sort(edges, 0, cnt, (a, b) -> a[2] - b[2]);int ans = 0;for (int i = 0; i < cnt; i++) {if (union(edges[i][0], edges[i][1])) {ans += edges[i][2];}}return ans;}public static int MAXN = 10010;public static int[][] edges = new int[MAXN << 1][3];public static int cnt;public static int[] father = new int[MAXN];public static void build(int n) {cnt = 0;for (int i = 0; i <= n; i++) {father[i] = i;}}public static int find(int i) {if (i != father[i]) {father[i] = find(father[i]);}return father[i];}// 如果x和y,原本是一個集合,返回false// 如果x和y,不是一個集合,合并之后后返回truepublic static boolean union(int x, int y) {int fx = find(x);int fy = find(y);if (fx != fy) {father[fx] = fy;return true;} else {return false;}}}
code4 1697. 檢查邊長度限制的路徑是否存在
// 檢查邊長度限制的路徑是否存在
// 給你一個 n 個點組成的無向圖邊集 edgeList
// 其中 edgeList[i] = [ui, vi, disi] 表示點 ui 和點 vi 之間有一條長度為 disi 的邊
// 請注意,兩個點之間可能有 超過一條邊 。
// 給你一個查詢數組queries ,其中 queries[j] = [pj, qj, limitj]
// 你的任務是對于每個查詢 queries[j] ,判斷是否存在從 pj 到 qj 的路徑
// 且這條路徑上的每一條邊都 嚴格小于 limitj 。
// 請你返回一個 布爾數組 answer ,其中 answer.length == queries.length
// 當 queries[j] 的查詢結果為 true 時, answer 第 j 個值為 true ,否則為 false
// 測試鏈接 : https://leetcode.cn/problems/checking-existence-of-edge-length-limited-paths/
package class061;import java.util.Arrays;// 檢查邊長度限制的路徑是否存在
// 給你一個 n 個點組成的無向圖邊集 edgeList
// 其中 edgeList[i] = [ui, vi, disi] 表示點 ui 和點 vi 之間有一條長度為 disi 的邊
// 請注意,兩個點之間可能有 超過一條邊 。
// 給你一個查詢數組queries ,其中 queries[j] = [pj, qj, limitj]
// 你的任務是對于每個查詢 queries[j] ,判斷是否存在從 pj 到 qj 的路徑
// 且這條路徑上的每一條邊都 嚴格小于 limitj 。
// 請你返回一個 布爾數組 answer ,其中 answer.length == queries.length
// 當 queries[j] 的查詢結果為 true 時, answer 第 j 個值為 true ,否則為 false
// 測試鏈接 : https://leetcode.cn/problems/checking-existence-of-edge-length-limited-paths/
public class Code04_CheckingExistenceOfEdgeLengthLimit {public static boolean[] distanceLimitedPathsExist(int n, int[][] edges, int[][] queries) {Arrays.sort(edges, (a, b) -> a[2] - b[2]);int m = edges.length;int k = queries.length;for (int i = 0; i < k; i++) {questions[i][0] = queries[i][0];questions[i][1] = queries[i][1];questions[i][2] = queries[i][2];questions[i][3] = i;}Arrays.sort(questions, 0, k, (a, b) -> a[2] - b[2]);build(n);boolean[] ans = new boolean[k];for (int i = 0, j = 0; i < k; i++) {// i : 問題編號// j : 邊的編號for (; j < m && edges[j][2] < questions[i][2]; j++) {union(edges[j][0], edges[j][1]);}ans[questions[i][3]] = isSameSet(questions[i][0], questions[i][1]);}return ans;}public static int MAXN = 100001;public static int[][] questions = new int[MAXN][4];public static int[] father = new int[MAXN];public static void build(int n) {for (int i = 0; i < n; i++) {father[i] = i;}}public static int find(int i) {if (i != father[i]) {father[i] = find(father[i]);}return father[i];}public static boolean isSameSet(int x, int y) {return find(x) == find(y);}public static void union(int x, int y) {father[find(x)] = find(y);}}
code5 P2330 [SCOI2005] 繁忙的都市
// 繁忙的都市
// 一個非常繁忙的大都市,城市中的道路十分的擁擠,于是市長決定對其中的道路進行改造
// 城市的道路是這樣分布的:城市中有n個交叉路口,有些交叉路口之間有道路相連
// 兩個交叉路口之間最多有一條道路相連接,這些道路是雙向的
// 且把所有的交叉路口直接或間接的連接起來了
// 每條道路都有一個分值,分值越小表示這個道路越繁忙,越需要進行改造
// 但是市政府的資金有限,市長希望進行改造的道路越少越好,于是他提出下面的要求:
// 1. 改造的那些道路能夠把所有的交叉路口直接或間接的連通起來
// 2. 在滿足要求1的情況下,改造的道路盡量少
// 3. 在滿足要求1、2的情況下,改造的那些道路中分值最大的道路分值盡量小
// 作為市規劃局的你,應當作出最佳的決策,選擇哪些道路應當被修建
// 返回選出了幾條道路 以及 分值最大的那條道路的分值是多少
// 測試鏈接 : https://www.luogu.com.cn/problem/P2330
// 請同學們務必參考如下代碼中關于輸入、輸出的處理
// 這是輸入輸出處理效率很高的寫法
// 提交以下所有代碼,把主類名改成Main,可以直接通過
package class061;// 繁忙的都市
// 一個非常繁忙的大都市,城市中的道路十分的擁擠,于是市長決定對其中的道路進行改造
// 城市的道路是這樣分布的:城市中有n個交叉路口,有些交叉路口之間有道路相連
// 兩個交叉路口之間最多有一條道路相連接,這些道路是雙向的
// 且把所有的交叉路口直接或間接的連接起來了
// 每條道路都有一個分值,分值越小表示這個道路越繁忙,越需要進行改造
// 但是市政府的資金有限,市長希望進行改造的道路越少越好,于是他提出下面的要求:
// 1. 改造的那些道路能夠把所有的交叉路口直接或間接的連通起來
// 2. 在滿足要求1的情況下,改造的道路盡量少
// 3. 在滿足要求1、2的情況下,改造的那些道路中分值最大的道路分值盡量小
// 作為市規劃局的你,應當作出最佳的決策,選擇哪些道路應當被修建
// 返回選出了幾條道路 以及 分值最大的那條道路的分值是多少
// 測試鏈接 : https://www.luogu.com.cn/problem/P2330
// 請同學們務必參考如下代碼中關于輸入、輸出的處理
// 這是輸入輸出處理效率很高的寫法
// 提交以下所有代碼,把主類名改成Main,可以直接通過import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.io.StreamTokenizer;
import java.util.Arrays;public class Code05_BusyCities {public static int MAXN = 301;public static int MAXM = 8001;public static int[] father = new int[MAXN];public static int[][] edges = new int[MAXM][3];public static int n, m;public static void build() {for (int i = 1; i <= n; i++) {father[i] = i;}}public static int find(int i) {if (i != father[i]) {father[i] = find(father[i]);}return father[i];}// 如果x和y本來就是一個集合,返回false// 如果x和y不是一個集合,合并之后返回truepublic static boolean union(int x, int y) {int fx = find(x);int fy = find(y);if (fx != fy) {father[fx] = fy;return true;} else {return false;}}public static void main(String[] args) throws IOException {BufferedReader br = new BufferedReader(new InputStreamReader(System.in));StreamTokenizer in = new StreamTokenizer(br);PrintWriter out = new PrintWriter(new OutputStreamWriter(System.out));while (in.nextToken() != StreamTokenizer.TT_EOF) {n = (int) in.nval;in.nextToken();m = (int) in.nval;build();for (int i = 0; i < m; i++) {in.nextToken();edges[i][0] = (int) in.nval;in.nextToken();edges[i][1] = (int) in.nval;in.nextToken();edges[i][2] = (int) in.nval;}Arrays.sort(edges, 0, m, (a, b) -> a[2] - b[2]);int ans = 0;int edgeCnt = 0;for (int[] edge : edges) {if (union(edge[0], edge[1])) {edgeCnt++;ans = Math.max(ans, edge[2]);}if (edgeCnt == n - 1) {break;}}out.println((n - 1) + " " + ans);}out.flush();out.close();br.close();}}
2023-12-8 14:22:17