leetcode刷题日记-【LCP 46. 志愿者调配】

251 阅读3分钟

携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第31天,点击查看活动详情 题目描述

「力扣挑战赛」有 n 个比赛场馆(场馆编号从 0 开始),场馆之间的通道分布情况记录于二维数组 edges 中,edges[i]= [x, y] 表示第 i 条通道连接场馆 x 和场馆 y(即两个场馆相邻)。初始每个场馆中都有一定人数的志愿者(不同场馆人数可能不同),后续 m 天每天均会根据赛事热度进行志愿者人数调配。调配方案分为如下三种:

将编号为 idx 的场馆内的志愿者人数减半; 将编号为 idx 的场馆相邻的场馆的志愿者人数都加上编号为 idx 的场馆的志愿者人数; 将编号为 idx 的场馆相邻的场馆的志愿者人数都减去编号为 idx 的场馆的志愿者人数。 所有的调配信息记录于数组 plans 中,plans[i] = [num,idx] 表示第 i 天对编号 idx 的场馆执行了第 num 种调配方案。 在比赛结束后对调配方案进行复盘时,不慎将第 0 个场馆的最终志愿者人数丢失,只保留了初始所有场馆的志愿者总人数 totalNum ,以及记录了第 1 ~ n-1 个场馆的最终志愿者人数的一维数组 finalCnt。请你根据现有的信息求出初始每个场馆的志愿者人数,并按场馆编号顺序返回志愿者人数列表。

注意:

测试数据保证当某场馆进行第一种调配时,该场馆的志愿者人数一定为偶数; 测试数据保证当某场馆进行第三种调配时,该场馆的相邻场馆志愿者人数不为负数; 测试数据保证比赛开始时每个场馆的志愿者人数都不超过 10^9; 测试数据保证给定的场馆间的道路分布情况中不会出现自环、重边的情况。 示例 1:

image.png

输入: finalCnt = [1,16], totalNum = 21, edges = [[0,1],[1,2]], plans = [[2,1],[1,0],[3,0]]

输出:[5,7,9]

解释:

image.png

示例 2 :

输入: finalCnt = [4,13,4,3,8], totalNum = 54, edges = [[0,3],[1,3],[4,3],[2,3],[2,5]], plans = [[1,1],[3,3],[2,5],[1,0]]

输出:[10,16,9,4,7,8]

提示:

  • 2 <= n <= 5*10^4
  • 1 <= edges.length <= min((n * (n - 1)) / 2, 5*10^4)
  • 0 <= edges[i][0], edges[i][1] < n
  • 1 <= plans.length <= 10
  • 1 <= plans[i][0] <=3
  • 0 <= plans[i][1] < n
  • finalCnt.length = n-1
  • 0 <= finalCnt[i] < 10^9
  • 0 <= totalNum < 5*10^13

解题思路

给定一个数组finalCnt为除了第0个场馆之外的所有场馆的最终志愿者人数,totalNum为初始志愿者总数,edges为每条通道场馆相邻的场馆,plans为每一天每个场馆选择的方案。要求初始每个场馆的人数。

首先确定每个场馆的志愿者人数有三个方案

  • 当前场馆的志愿者人数减少一半;
  • 当前场馆相邻的所有场馆志愿者人数增加当前场馆的人数;
  • 当前场馆相邻的所有场馆志愿者人数减少当前场馆的人数;

所以需要先获取到场馆的相邻场馆,使用一个map的容器来封装场馆间的相邻场馆,命名为neighbors。

然后根据plans和finalCnt倒推初始的场馆志愿者人数,这里因为不确0号场馆的最终志愿者人数,所以可以用回溯的方法来求得最初的志愿者人数。

确定本题的回溯三要素

路径

这里的路径其实就只有一个值,即当前0号场馆选择的人数

可选择路径

0号场馆最终志愿者人数的范围[0,finalCnt[0]*plans.size]

终止条件

当选择的方案与当前场馆人数不匹配时,直接进行下次回溯;如果已经到达第一天的方案选择且初始志愿者人数之和等于totalNum时,返回当前每个场馆的志愿者人数。

优化思路

超出时间限制,先判断能否进行剪枝,或者缩小场馆0的取值范围;

可以将场馆0的最终志愿者数量设置为变量x,然后每次计算都代入计算x,就不计算具体值,最终的初始志愿者数量可以通过解一元一次方程解答;

所以优化后的代码不从遍历所有0号场馆可能的值入手,而是直接采用假设变量法,即假定它是一个变量。则操作这个场馆及相邻场馆的志愿者人数分为两部分,一部分是已知的常量,另一部分是未知数系数。最后通过一元一次方程求解得到未知数。然后获得最终志愿者人数。

代码实现

public static int[] volunteerDeployment(int[] finalCnt, long totalNum, int[][] edges, int[][] plans) {
    // 封装相邻场馆map
    Map<Integer, HashSet<Integer>> neighbors = new HashMap<>();
    for (int[] edge : edges) {
        neighbors.putIfAbsent(edge[0], new HashSet<>());
        neighbors.get(edge[0]).add(edge[1]);
        neighbors.putIfAbsent(edge[1], new HashSet<>());
        neighbors.get(edge[1]).add(edge[0]);
    }
    // 所有场馆当前的人数
    long[] res = new long[finalCnt.length + 1];
    for (int i=0;i< finalCnt.length;i++) {
        res[i+1] = finalCnt[i];
    }
    // 每个场馆的变量系数
    double[] variable = new double[finalCnt.length + 1];
    // 第0个场馆,系数初始为1
    variable[0] = 1.0;
    for (int i = plans.length-1;i>= 0;i--) {
        int[] plan = plans[i];
        int item = plan[1];
        if (plan[0] == 1) {
            // 本身加一半
            res[item] *= 2;
            // 变量也要做相应的变动
            variable[item] *= 2;
        } else if (plan[0] == 2) {
            // 相邻的所有元素减当前场馆元素
            for (Integer integer : neighbors.getOrDefault(item, new HashSet<>())) {
                res[integer] -= res[item];
                // 变量也要做相应的变动
                variable[integer] -= variable[item];
            }
        } else {
            // 相邻的所有元素加当前场馆元素
            for (Integer integer : neighbors.getOrDefault(item, new HashSet<>())) {
                res[integer] += res[item];
                // 变量也要做相应的变动
                variable[integer] += variable[item];
            }
        }
    }
    // 求解一元一次方程
    long sum = 0;
    double variableSum = 0;
    for (int i =0;i < res.length; i ++) {
        sum += res[i];
        variableSum += variable[i];
    }
    double x = (double)(totalNum-sum)/variableSum;
    int[] returnRes = new int[res.length];
    // 封装结果
    for (int i =0;i < res.length; i ++) {
        returnRes[i] = (int)(res[i] + x*variable[i]);
    }
    return returnRes;
}