牛客题解 | 最小生成树

题目## 题目

题目链接

解题思路:

  1. 使用 K r u s k a l Kruskal Kruskal 算法求最小生成树:

    • 将所有边按权重排序
    • 使用并查集维护节点的连通性
    • 依次选择权重最小的边,如果不会形成环就加入最小生成树
  2. 并查集优化:

    • 使用路径压缩优化 find 操作
    • 使用按秩合并优化 union 操作
  3. 算法步骤:

    • 初始化并查集
    • 对边按权重排序
    • 遍历边,使用并查集判断是否形成环
    • 统计总成本和使用的边数
    • 检查图的连通性

代码


class Solution {
public:
    // 并查集数据结构
    class UnionFind {
    private:
        vector<int> parent;
        vector<int> rank;
        
    public:
        UnionFind(int n) {
            parent.resize(n + 1);
            rank.resize(n + 1);
            for(int i = 0; i <= n; i++) {
                parent[i] = i;
                rank[i] = 1;
            }
        }
        
        int find(int x) {
            if(parent[x] != x) {
                parent[x] = find(parent[x]); // 路径压缩
            }
            return parent[x];
        }
        
        bool unite(int x, int y) {
            int rootX = find(x);
            int rootY = find(y);
            if(rootX == rootY) {
                return false;
            }
            
            // 按秩合并
            if(rank[rootX] < rank[rootY]) {
                swap(rootX, rootY);
            }
            parent[rootY] = rootX;
            if(rank[rootX] == rank[rootY]) {
                rank[rootX]++;
            }
            return true;
        }
    };
    
    int miniSpanningTree(int n, int m, vector<vector<int> >& cost) {
        // 将边按照权重排序
        sort(cost.begin(), cost.end(), 
             [](const vector<int>& a, const vector<int>& b) {
                 return a[2] < b[2];
             });
        
        UnionFind uf(n);
        int totalCost = 0;
        int edgesUsed = 0;
        
        // Kruskal算法
        for(const auto& edge : cost) {
            int from = edge[0];
            int to = edge[1];
            int weight = edge[2];
            
            if(uf.unite(from, to)) {
                totalCost += weight;
                edgesUsed++;
                
                if(edgesUsed == n - 1) {
                    break;
                }
            }
        }
        
        // 检查是否所有节点都已连接
        int root = uf.find(1);
        for(int i = 2; i <= n; i++) {
            if(uf.find(i) != root) {
                return -1;
            }
        }
        
        return totalCost;
    }
};
import java.util.*;

public class Solution {
    // 并查集数据结构
    class UnionFind {
        private int[] parent;
        private int[] rank;
        
        public UnionFind(int n) {
            parent = new int[n + 1];
            rank = new int[n + 1];
            for (int i = 0; i <= n; i++) {
                parent[i] = i;
                rank[i] = 1;
            }
        }
        
        public int find(int x) {
            if (parent[x] != x) {
                parent[x] = find(parent[x]); // 路径压缩
            }
            return parent[x];
        }
        
        public boolean union(int x, int y) {
            int rootX = find(x);
            int rootY = find(y);
            if (rootX == rootY) {
                return false;
            }
            
            // 按秩合并
            if (rank[rootX] < rank[rootY]) {
                int temp = rootX;
                rootX = rootY;
                rootY = temp;
            }
            parent[rootY] = rootX;
            if (rank[rootX] == rank[rootY]) {
                rank[rootX]++;
            }
            return true;
        }
    }
    
    public int miniSpanningTree(int n, int m, int[][] cost) {
        // 将边按照权重排序
        Arrays.sort(cost, (a, b) -> a[2] - b[2]);
        
        UnionFind uf = new UnionFind(n);
        int totalCost = 0;
        int edgesUsed = 0;
        
        // Kruskal算法
        for (int[] edge : cost) {
            int from = edge[0];
            int to = edge[1];
            int weight = edge[2];
            
            // 如果这条边不会形成环,则加入最小生成树
            if (uf.union(from, to)) {
                totalCost += weight;
                edgesUsed++;
                
                // 如果已经使用了n-1条边,说明最小生成树已经完成
                if (edgesUsed == n - 1) {
                    break;
                }
            }
        }
        
        // 检查是否所有节点都已连接
        int root = uf.find(1);
        for (int i = 2; i <= n; i++) {
            if (uf.find(i) != root) {
                return -1; // 图不连通
            }
        }
        
        return totalCost;
    }
}
class Solution:
    class UnionFind:
        def __init__(self, n):
            self.parent = list(range(n + 1))
            self.rank = [1] * (n + 1)
        
        def find(self, x):
            if self.parent[x] != x:
                self.parent[x] = self.find(self.parent[x])  # 路径压缩
            return self.parent[x]
        
        def union(self, x, y):
            root_x = self.find(x)
            root_y = self.find(y)
            if root_x == root_y:
                return False
            
            # 按秩合并
            if self.rank[root_x] < self.rank[root_y]:
                root_x, root_y = root_y, root_x
            self.parent[root_y] = root_x
            if self.rank[root_x] == self.rank[root_y]:
                self.rank[root_x] += 1
            return True
    
    def miniSpanningTree(self, n: int, m: int, cost: List[List[int]]) -> int:
        # 将边按照权重排序
        cost.sort(key=lambda x: x[2])
        
        uf = self.UnionFind(n)
        total_cost = 0
        edges_used = 0
        
        # Kruskal算法
        for from_node, to_node, weight in cost:
            if uf.union(from_node, to_node):
                total_cost += weight
                edges_used += 1
                
                if edges_used == n - 1:
                    break
        
        # 检查是否所有节点都已连接
        root = uf.find(1)
        for i in range(2, n + 1):
            if uf.find(i) != root:
                return -1
        
        return total_cost

算法及复杂度分析:

  • 算法 K r u s k a l Kruskal Kruskal 算法,并查集
  • 时间复杂度 O ( m log ⁡ m ) \mathcal{O}(m \log m) O(mlogm)
    • 排序需要 O ( m log ⁡ m ) \mathcal{O}(m \log m) O(mlogm)
    • 并查集操作接近 O ( 1 ) \mathcal{O}(1) O(1)
  • 空间复杂度 O ( n ) \mathcal{O}(n) O(n)
    • 并查集需要 O ( n ) \mathcal{O}(n) O(n) 的空间

注意事项:

  1. 需要检查图是否连通
  2. 最小生成树应该有 n − 1 n-1 n1 条边
  3. 使用并查集时要注意路径压缩和按秩合并的优化
  4. 边的权重可能相同,但不影响算法正确性

题目链接

解题思路

这是一个二维最大子数组和的问题,可以通过以下步骤解决:

  1. 对于每一对可能的行( i , j i, j i,j),我们计算这些行之间每一列的元素和
  2. 将二维问题转化为一维问题:把 i i i j j j 行的每一列的和看作一个一维数组
  3. 对这个一维数组求最大子数组和(使用 Kadane \text{Kadane} Kadane 算法)
  4. 遍历所有可能的行对,找到最大的和

代码

#include <iostream>
#include <vector>
#include <climits>
using namespace std;

int kadane(vector<int>& arr) {
    int maxSoFar = arr[0], maxEndingHere = arr[0];
    for(int i = 1; i < arr.size(); i++) {
        maxEndingHere = max(arr[i], maxEndingHere + arr[i]);
        maxSoFar = max(maxSoFar, maxEndingHere);
    }
    return maxSoFar;
}

int maxSubmatrixSum(vector<vector<int>>& matrix) {
    int n = matrix.size();
    int maxSum = INT_MIN;
    
    // 遍历所有可能的行对
    for(int i = 0; i < n; i++) {
        vector<int> colSum(n, 0);
        for(int j = i; j < n; j++) {
            // 计算i到j行之间每列的和
            for(int k = 0; k < n; k++) {
                colSum[k] += matrix[j][k];
            }
            // 对colSum使用Kadane算法
            maxSum = max(maxSum, kadane(colSum));
        }
    }
    return maxSum;
}

int main() {
    int n;
    cin >> n;
    vector<vector<int>> matrix(n, vector<int>(n));
    
    for(int i = 0; i < n; i++) {
        for(int j = 0; j < n; j++) {
            cin >> matrix[i][j];
        }
    }
    
    cout << maxSubmatrixSum(matrix) << endl;
    return 0;
}
import java.util.Scanner;

public class Main {
    public static int kadane(int[] arr) {
        int maxSoFar = arr[0], maxEndingHere = arr[0];
        for(int i = 1; i < arr.length; i++) {
            maxEndingHere = Math.max(arr[i], maxEndingHere + arr[i]);
            maxSoFar = Math.max(maxSoFar, maxEndingHere);
        }
        return maxSoFar;
    }
    
    public static int maxSubmatrixSum(int[][] matrix) {
        int n = matrix.length;
        int maxSum = Integer.MIN_VALUE;
        
        for(int i = 0; i < n; i++) {
            int[] colSum = new int[n];
            for(int j = i; j < n; j++) {
                for(int k = 0; k < n; k++) {
                    colSum[k] += matrix[j][k];
                }
                maxSum = Math.max(maxSum, kadane(colSum));
            }
        }
        return maxSum;
    }
    
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        int n = sc.nextInt();
        int[][] matrix = new int[n][n];
        
        for(int i = 0; i < n; i++) {
            for(int j = 0; j < n; j++) {
                matrix[i][j] = sc.nextInt();
            }
        }
        
        System.out.println(maxSubmatrixSum(matrix));
        sc.close();
    }
}
def kadane(arr):
    max_so_far = max_ending_here = arr[0]
    for x in arr[1:]:
        max_ending_here = max(x, max_ending_here + x)
        max_so_far = max(max_so_far, max_ending_here)
    return max_so_far

def max_submatrix_sum(matrix):
    n = len(matrix)
    max_sum = float('-inf')
    
    for i in range(n):
        col_sum = [0] * n
        for j in range(i, n):
            for k in range(n):
                col_sum[k] += matrix[j][k]
            max_sum = max(max_sum, kadane(col_sum))
    
    return max_sum

n = int(input())
matrix = []
for _ in range(n):
    row = list(map(int, input().split()))
    matrix.append(row)

print(max_submatrix_sum(matrix))

算法及复杂度

  • 算法:动态规划( Kadane \text{Kadane} Kadane 算法)+ 矩阵前缀和
  • 时间复杂度: O ( n 3 ) \mathcal{O}(n^3) O(n3),其中 n n n 是矩阵的边长
  • 空间复杂度: O ( n ) \mathcal{O}(n) O(n),用于存储列和数组
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值