最小化旅行的价格总和

1. 深度优先搜索 + 深度优先搜索

分析:选择不相邻节点减半价格并最小化价格
涉及到图的选择的问题,考虑使用回溯法,选择为当前节点减半或者不减半,并递归搜索相邻节点
不过该题首先要知道哪些点走了几次,可以根据路径选择事先用回溯法得到每个点的访问次数
然后根据访问次数与价格生成权重,使用权重做后面的减半回溯搜索,找到最小化解

class Solution {
public:
    vector<vector<int>> graph;//无向邻接表
    vector<int> cnt;//走完指定路径每个点通过的次数
    vector<int> weight;//顶点的代价
    bool confirm;
    int minimumTotalPrice(int n, vector<vector<int>>& edges, vector<int>& price, vector<vector<int>>& trips) {
        graph.resize(n);
        for (auto& edge : edges) {//初始化
            int from = edge[0];
            int to = edge[1];
            graph[from].push_back(to);
            graph[to].push_back(from);
        }

        cnt.resize(n); //记录每个点经历过的次数
        for (auto& edge : trips) {//用路径初始化经过点的次数
            int from = edge[0];
            int to = edge[1];
            confirm = 0;//当前路径是否找到,用于剪枝
            dfs(from, to, -1);//根据起点终点对经过的点计数
        }

        weight.resize(n); //根据经历次数和点代价计算加权权值
        for (int i = 0; i < n; i++) 
            weight[i] = cnt[i] * price[i];//计算某顶点的代价
        int res = reduce(0, 0, -1);//再次深度优先计算最小路径和
        return res;
    }
    //用于计算起点到终点经过了哪些顶点
    void dfs(int start, int end, int from) {//计算经过各顶点的次数
        if(start==end){cnt[end]++; confirm = 1; return;} //对终点计数一次

        if (confirm) return;//如果其他路径找到了,就不需要再找了,直接剪枝
        cnt[start]++;//选择当前路径,计数起点

        int n = graph[start].size();
        for (int i = 0; i < n&& !confirm; i++) { //遍历邻接顶点,寻找到达目的路径
            if (graph[start][i] == from) continue;//不从原路返回
            dfs(graph[start][i], end, start);//选择下一个节点作为起始节点
        }
        if (confirm) return;//当前路径找到了,不需要再撤回
        cnt[start]--;//撤销选择
    }
    long reduce(bool before, int cur, int from) {
        int n = graph[cur].size();
        if (n == 1 && graph[cur][0] == from) {//边界条件,只有一条边,且刚从那边过来
            if (before == 0) return weight[cur] / 2;
            else return weight[cur];
        }
        //权重为0无所谓区不区分,全按不减半处理
        if(weight[cur]==0) {
            int res = 0;
            for (int i = 0; i < n; i++) {//当前节点减半
                if (graph[cur][i] == from) continue;
                res += reduce(0, graph[cur][i], cur);
            }
            return res;
        } 
        //当前节点减半
        int half = weight[cur] / 2;
        if (before == 0) {//前一个节点没有减半
            for (int i = 0; i < n; i++) {//当前节点减半
                if (graph[cur][i] == from) continue;
                half += reduce(1, graph[cur][i], cur);
            }
        }
        //当前节点全值
        int total = weight[cur];//当前节点不减半
        for (int i = 0; i < n; i++) {
            if (graph[cur][i] == from) continue;
            total += reduce(0, graph[cur][i], cur);
        }
        return before?total:min(half, total);
 } 
};

2. 深度优先 + 树形dp

在方法一中,第二次深度优先暴力搜索所有减半不减半的情况
实际上从下往上记录折半与不折半最小值,可以不用遍历搜索所有情况

class Solution {
public:
    int minimumTotalPrice(int n, vector<vector<int>> &edges, vector<int> &price, vector<vector<int>> &trips) {
        vector<vector<int>> g(n);
        for (auto &e: edges) {
            int x = e[0], y = e[1];
            g[x].push_back(y);
            g[y].push_back(x); // 建树
        }

        int cnt[n]; memset(cnt, 0, sizeof(cnt));
        for (auto &t: trips) {
            int end = t[1];
            function<bool(int, int)> dfs = [&](int x, int fa) -> bool {
                if (x == end) { // 到达终点(注意树只有唯一的一条简单路径)
                    ++cnt[x]; // 统计从 start 到 end 的路径上的点经过了多少次
                    return true; // 找到终点
                }
                for (int y: g[x])
                    if (y != fa && dfs(y, x)) {
                        ++cnt[x]; // 统计从 start 到 end 的路径上的点经过了多少次
                        return true; // 找到终点
                    }
                return false; // 未找到终点
            };
            dfs(t[0], -1);
        }

        function<pair<int, int>(int, int)> dfs = [&](int x, int fa) -> pair<int, int> {
            int not_halve = price[x] * cnt[x]; // x 不变
            int halve = not_halve / 2; // x 减半
            for (int y: g[x])
                if (y != fa) {
                    auto [nh, h] = dfs(y, x); // 计算 y 不变/减半的最小价值总和
                    not_halve += min(nh, h); // x 不变,那么 y 可以不变,可以减半,取这两种情况的最小值
                    halve += nh; // x 减半,那么 y 只能不变
                }
            return {not_halve, halve};
        };
        auto [nh, h] = dfs(0, -1);
        return min(nh, h);
    }
};


posted @ 2023-04-16 19:54  失控D大白兔  阅读(10)  评论(0编辑  收藏  举报