1 solutions

  • 0
    @ 2024-11-6 20:06:23

    题目描述

    消防员需要在城市中铺设消防栓。城市的道路可以看作是一棵连通且无回路的树,每个节点代表一个底座,边代表道路。

    1.消防栓必须安装在底座上 => 消防栓只能放置在树的节点上

    2.每条道路必须有消防栓盖 => 任何一条边都需要满足:所连接的两个节点至少有一个节点上有消防栓。

    3.交叉路口只有一个消防栓底座,可以覆盖所有连接的道路 =》 每个节点最多放置一个消防栓,放置了就可以覆盖所有其他节点。

    4.求覆盖所有道路所需的最少消防栓数量。 =》

    给定一棵树,求选出最少的节点集合setset , 使得a:每一个节点至少有一个邻居属于集合setset,且b:任意一条边所连接的两个节点至少有一个节点属于集合setset

    我们发现b 包含a 。 所以就是:给定一棵树,求选出最少的节点集合setset , 使得任意一条边所连接的两个节点至少有一个节点属于集合setset

    思路

    要解决这个问题,最终我们需要将其转化为树的最小顶点覆盖问题,并利用动态规划进行高效求解。具体而言,对树进行深度优先搜索(DFS),为每个节点维护两个状态:选中该节点(铺设消防栓)和不选中该节点。若选中当前节点,其子节点可以选择或不选择,取最小值;若不选中当前节点,则所有子节点必须被选中以覆盖相应的道路。通过这种方式深入分析每个节点的最优选择,最终在根节点处得到覆盖所有道路所需的最少消防栓数量。

    • 通过 DFS 遍历树,并计算每个节点的 dp 值。
      • dp[u][1] 表示节点 u 被选中,其值为 1 加上所有子节点的最小覆盖数(子节点可以选或不选,取最小值)。
      • dp[u][0] 表示节点 u 不被选中,其值为所有子节点必须被选中的覆盖数。

    最终答案就是min(dp[0][0],dp[0][1])min(dp[0][0],dp[0][1])

    cpp

    #include <bits/stdc++.h>
    using namespace std;
    
    // 定义最大节点数
    const int MAX = 1505;
    
    // 全局变量
    vector<vector<int>> adj(MAX); // 邻接表
    int dp_val[MAX][2]; // dp[u][0]: u不被选中, dp[u][1]: u被选中
    bool visited_flag[MAX]; // 访问标记
    
    // 深度优先搜索计算dp值
    int dfs(int u) {
        visited_flag[u] = true;
        dp_val[u][0] = 0; // u不被选中时,初始化为0
        dp_val[u][1] = 1; // u被选中时,初始化为1
        for(auto &v: adj[u]){
            if(!visited_flag[v]){
                dfs(v); // 递归计算子节点
                dp_val[u][1] += min(dp_val[v][0], dp_val[v][1]); // u被选中,子节点可选或不选,取最小值
                dp_val[u][0] += dp_val[v][1]; // u不被选中,子节点必须被选中
            }
        }
        return min(dp_val[u][0], dp_val[u][1]); // 返回当前节点的最小覆盖数
    }
    
    int main(){
        // 优化输入速度
        ios::sync_with_stdio(false);
        cin.tie(NULL);
    
        int n;
        // 读取直到文件结束(处理多组测试用例)
        while(cin >> n){
            // 清空邻接表
            for(int i=0;i<n;i++) adj[i].clear();
            // 读取n行描述
            for(int i=0;i<n;i++){
                string line;
                cin >> ws; // 读取前导空格
                getline(cin, line);
                // 解析格式 a:(b) x1 x2 ... xb
                int a, b;
                size_t pos1 = line.find(":(");
                a = stoi(line.substr(0, pos1));
                size_t pos2 = line.find(")", pos1);
                b = stoi(line.substr(pos1+2, pos2 - pos1 -2));
                // 读取后面的b个数字
                if(b > 0){
                    size_t start = pos2 + 1;
                    // 提取子节点编号
                    while(start < line.size() && line[start] == ' ') start++; // 跳过空格
                    string nums = line.substr(start);
                    // 分割数字
                    int v;
                    stringstream ss(nums);
                    while(ss >> v){
                        adj[a].push_back(v);
                        adj[v].push_back(a); // 无向图
                    }
                }
            }
            // 重置访问标记
            fill(visited_flag, visited_flag + n, false);
            // 处理极端情况:只有一个节点
            if(n == 1){
                cout << "1\n";
                continue;
            }
            // 假设根节点为0
            cout << dfs(0) << "\n";
        }
    }
    
    

    java

    import java.util.*;
    import java.io.*;
    
    public class Main {
        static int n;
        static ArrayList<ArrayList<Integer>> adj;
        static int[][] dp;
        static boolean[] visited;
    
        public static void main(String[] args) throws IOException{
            BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
            String line;
            // 读取直到文件结束(处理多组测试用例)
            while((line = br.readLine()) != null){
                line = line.trim();
                if(line.equals("")) continue;
                n = Integer.parseInt(line);
                adj = new ArrayList<>();
                for(int i=0;i<n;i++) adj.add(new ArrayList<>());
                // 读取n行描述
                for(int i=0;i<n;i++){
                    String desc = br.readLine();
                    if(desc == null || desc.trim().equals("")) continue;
                    desc = desc.trim();
                    int pos1 = desc.indexOf(":(");
                    int a = Integer.parseInt(desc.substring(0, pos1));
                    int pos2 = desc.indexOf(")", pos1);
                    int b = Integer.parseInt(desc.substring(pos1+2, pos2));
                    if(b > 0){
                        String connectionsStr = desc.substring(pos2+1).trim();
                        if(!connectionsStr.equals("")){
                            String[] connections = connectionsStr.split(" ");
                            for(int j=0; j<connections.length; j++){
                                if(connections[j].equals("")) continue;
                                int v = Integer.parseInt(connections[j]);
                                adj.get(a).add(v);
                                adj.get(v).add(a);
                            }
                        }
                    }
                }
                // 初始化DP和访问数组
                dp = new int[n][2];
                visited = new boolean[n];
                // 处理特殊情况
                if(n == 1){
                    System.out.println(1);
                    continue;
                }
                // 递归计算
                dfs(0);
                // 输出结果
                System.out.println(Math.min(dp[0][0], dp[0][1]));
            }
        }
    
        // 深度优先搜索计算DP值
        static void dfs(int u){
            visited[u] = true;
            dp[u][0] = 0; // u不被选中
            dp[u][1] = 1; // u被选中
            for(int v : adj.get(u)){
                if(!visited[v]){
                    dfs(v);
                    dp[u][1] += Math.min(dp[v][0], dp[v][1]); // u被选中,子节点可选或不选,取最小值
                    dp[u][0] += dp[v][1]; // u不被选中,子节点必须被选中
                }
            }
        }
    }
    

    python

    import sys
    from collections import defaultdict
    
    # 定义最大节点数
    MAX = 1505
    
    # 全局变量
    adj = defaultdict(list)  # 邻接表
    dp_val = [[0] * 2 for _ in range(MAX)]  # dp[u][0]: u不被选中, dp[u][1]: u被选中
    visited_flag = [False] * MAX  # 访问标记
    
    # 深度优先搜索计算dp值
    def dfs(u):
        visited_flag[u] = True
        dp_val[u][0] = 0  # u不被选中时,初始化为0
        dp_val[u][1] = 1  # u被选中时,初始化为1
        for v in adj[u]:
            if not visited_flag[v]:
                dfs(v)  # 递归计算子节点
                dp_val[u][1] += min(dp_val[v][0], dp_val[v][1])  # u被选中,子节点可选或不选,取最小值
                dp_val[u][0] += dp_val[v][1]  # u不被选中,子节点必须被选中
        return min(dp_val[u][0], dp_val[u][1])  # 返回当前节点的最小覆盖数
    
    def main():
        input = sys.stdin.read
        data = input().strip().splitlines()
        idx = 0
        while idx < len(data):
            n = int(data[idx].strip())
            idx += 1
            
            # 清空邻接表
            adj.clear()
            
            for i in range(n):
                line = data[idx].strip()
                idx += 1
                # 解析格式 a:(b) x1 x2 ... xb
                pos1 = line.find(":(")
                a = int(line[:pos1])
                pos2 = line.find(")", pos1)
                b = int(line[pos1 + 2:pos2])
                # 读取后面的b个数字
                if b > 0:
                    nums = line[pos2 + 1:].strip()
                    neighbors = list(map(int, nums.split()))
                    for v in neighbors:
                        adj[a].append(v)
                        adj[v].append(a)  # 无向图
            
            # 重置访问标记
            visited_flag[:] = [False] * n
            
            # 处理极端情况:只有一个节点
            if n == 1:
                print(1)
                continue
            
            # 假设根节点为0
            print(dfs(0))
    
    if __name__ == "__main__":
        main()
    
    
    • 1

    2024.11.6-秋招-第2题-铺设消防栓

    Information

    ID
    171
    Time
    1000ms
    Memory
    256MiB
    Difficulty
    8
    Tags
    # Submissions
    163
    Accepted
    33
    Uploaded By