(B卷,200分)- 组装最大可靠性设备(Java & JS & Python)

题目描述

一个设备由N种类型元器件组成(每种类型元器件只需要一个,类型type编号从0~N-1),

每个元器件均有可靠性属性reliability,可靠性越高的器件其价格price越贵。

而设备的可靠性由组成设备的所有器件中可靠性最低的器件决定。

给定预算S,购买N种元器件( 每种类型元器件都需要购买一个),在不超过预算的情况下,请给出能够组成的设备的最大可靠性。

输入描述

S N // S总的预算,N元器件的种类

total // 元器件的总数,每种型号的元器件可以有多种;

此后有total行具体器件的数据

type reliability price // type 整数类型,代表元器件的类型编号从0 ~ N-1; reliabilty 整数类型 ,代表元器件的可靠性; price 整数类型 ,代表元器件的价格

输出描述

符合预算的设备的最大可靠性,如果预算无法买齐N种器件,则返回 -1

备注

  • 0 <= S,price <= 10000000
  • 0 <= N <= 100
  • 0 <= type <= N-1
  • 0 <= total <= 100000
  • 0 < reliability <= 100000

用例

输入 500 3
6
0 80 100
0 90 200
1 50 50
1 70 210
2 50 100
2 60 150
输出 60
说明

预算500,设备需要3种元件组成,方案

类型0的第一个(可靠性80),

类型1的第二个(可靠性70),

类型2的第二个(可靠性60),

可以使设备的可靠性最大 60

输入 100 1
1
0 90 200
输出 -1
说明 组成设备需要1个元件,但是元件价格大于预算,因此无法组成设备,返回-1

题目解析

本题很像是分组背包问题,但是细看却不是,因为题目描述中说:

每种类型元器件都需要购买一个

我的解题思路是二分。

首先,不分种类,收集所有器件出现过的可靠性到一个Set集合中,收集完后,进行升序排序,升序后可靠性数组设为maybe,即maybe数组里面的可靠性都有可能是题解。

之后,将所有器件按类型统计,由于题目输入已经给出了种类数n,因此可以定义一个长度为n的数组kinds,即为种类数组,kinds[type]下记录对应类型的所有器件。统计完后,我们需要遍历每一个kinds[type],然后将它按照器件的可靠性进行升序。

预备处理如上。

接下来我们需要在maybe中二分取中间值maybe[mid],作为一个可能的题解,即设备最大可靠性。然后遍历kinds,在每一个种类的器件中,找到一个可靠性>=maybe[mid],且最接近maybe[mid]的器件,这里需要想清楚两点:

  • 题目描述:而设备的可靠性由组成设备的所有器件中可靠性最低的器件决定。因此,maybe[mid]作为题解的话,则每一个种类中选取的器件的可靠性要大于等于maybe[mid]才行。
  • 题目描述还说:可靠性越高的器件其价格price越贵。因此,按照贪心原则,我们选取一个大于等于且最接近maybe[mid]可靠性的器件,是花费最少的。这样省下来的钱就可以买其他器件。

由于之前,已经对kinds[type]进行了升序,因此这里可以二分查找:可靠性>=maybe[mid],且最接近maybe[mid]的器件

这里又涉及到二分查找的目标位置和有序插入位置的知识,具体可以看:

当找到所有kinds[type]中可靠性>=maybe[mid],且最接近maybe[mid]的器件后,对这些器件的price进行求和,得到sumPrice:

  • 如果sumPrice <= 总预算s,那么说明,当前maybe[mid]真的是一个可能解,但不一定是最优解,我们需要继续二分找到更小的maybe[mid]
  • 如果sumPrice > 总预算s,那么说明,当前maybe[mid]选大了,我们下次二分应该选更小一点的maybe[mid]来尝试

按此二分逻辑,将最后一次符合要求的maybe[mid]可能解返回即可,如果没有符合要求的maybe[mid],则返回-1。

Java算法源码

import java.util.ArrayList;
import java.util.Scanner;
import java.util.TreeSet;

public class Main {
  // 器件类
  static class Device {
    int reliability;
    int price;

    public Device(int reliability, int price) {
      this.reliability = reliability;
      this.price = price;
    }
  }

  public static void main(String[] args) {
    Scanner sc = new Scanner(System.in);

    int s = sc.nextInt(); // 总预算
    int n = sc.nextInt(); // 器件的种类数

    // 收集器件的可靠性
    TreeSet<Integer> reliabilities = new TreeSet<>();

    // 各种类集合
    ArrayList<ArrayList<Device>> kinds = new ArrayList<>();
    // 为每个具体种类创建一个集合,用于装对应种类的器件
    for (int i = 0; i < n; i++) kinds.add(new ArrayList<>());

    int total = sc.nextInt(); // 之后输入total行具体器件的数据

    for (int i = 0; i < total; i++) {
      // 器件种类
      int type = sc.nextInt();

      // 器件可靠性
      int reliability = sc.nextInt();
      reliabilities.add(reliability); // 收集器件的可靠性

      // 器件价值
      int price = sc.nextInt();

      // 将器件加入到对应的种类中
      kinds.get(type).add(new Device(reliability, price));
    }

    System.out.println(getResult(s, kinds, reliabilities));
  }

  /**
   * @param s 总预算
   * @param kinds 种类集合
   * @param reliabilities 可靠性集合
   * @return 最大可靠性
   */
  public static int getResult(
      int s, ArrayList<ArrayList<Device>> kinds, TreeSet<Integer> reliabilities) {

    // ans记录题解
    int ans = -1;

    // 将每个种类内的器件按照可靠性升序
    for (ArrayList<Device> kind : kinds) {
      kind.sort((a, b) -> a.reliability - b.reliability);
    }

    // 将所有器件的可靠性集合,变为数组
    Integer[] maybe = reliabilities.toArray(new Integer[0]);

    // 二分选取可能的最大可靠性maybe
    int low = 0;
    int high = maybe.length - 1;

    while (low <= high) {
      int mid = (low + high) >> 1;
      // 如果maybe[mid]可靠性可以保证所有种类器件都能选到,且价格之和小于s
      if (check(kinds, maybe[mid], s)) {
        // 则maybe[mid]可靠性就是一个可能解
        ans = maybe[mid];
        // 继续尝试更优解,即找更大的可靠性
        low = mid + 1;
      } else {
        // 否则,说明可靠性选高了,我们应该继续尝试更低的可靠性
        high = mid - 1;
      }
    }

    return ans;
  }

  public static boolean check(ArrayList<ArrayList<Device>> kinds, int maxReliability, int s) {
    int sum = 0;
    for (ArrayList<Device> kind : kinds) {
      // 注意kind内的器件已经按照可靠性升序了
      // 我们需要在该kind种类内找到一个可靠性>=maxReliability的器件
      int idx = binarySearch(kind, maxReliability);

      // 如果idx<0,则说明idx是一个插入位置
      if (idx < 0) {
        idx = -idx - 1;
      }

      // 如果idx==kind.size()则说明kind内所有器件的可靠性都低于maxReliability,因此此kind器件选取不到,可以直接返回false
      if (idx == kind.size()) return false;

      // 否则,选取对应idx位置的器件,并计入价格到总价
      sum += kind.get(idx).price;
    }

    // 如果最终总价小于总预算s,则此maxReliability可行
    return sum <= s;
  }

  public static int binarySearch(ArrayList<Device> kind, int target) {
    int low = 0;
    int high = kind.size() - 1;

    while (low <= high) {
      int mid = (low + high) >> 1;
      Device device = kind.get(mid);

      if (device.reliability > target) {
        high = mid - 1;
      } else if (device.reliability < target) {
        low = mid + 1;
      } else {
        return mid;
      }
    }

    return -low - 1;
  }
}

JS算法源码

/* JavaScript Node ACM模式 控制台输入获取 */
const readline = require("readline");

const rl = readline.createInterface({
  input: process.stdin,
  output: process.stdout,
});

const lines = [];
let s, n, total;
rl.on("line", (line) => {
  lines.push(line);

  if (lines.length === 2) {
    [s, n] = lines[0].split(" ").map(Number);
    total = lines[1] - 0;
  }

  if (total !== undefined && lines.length === total + 2) {
    lines.shift();
    lines.shift();

    // 记录各种类
    const kinds = new Array(n).fill(0).map(() => new Array());

    // 记录所有器件的可靠性
    const reliabilities = new Set();

    lines
      .map((s) => s.split(" ").map(Number))
      .forEach((arr) => {
        const [type, reliability, price] = arr;
        // 将器件加入到对应的种类下
        kinds[type].push(new Device(reliability, price));
        // 收集该器件的可靠性
        reliabilities.add(reliability);
      });

    console.log(getResult(kinds, s, reliabilities));

    lines.length = 0;
  }
});

// 器件类
class Device {
  constructor(reliability, price) {
    this.reliability = reliability;
    this.price = price;
  }
}

/**
 *
 * @param {*} kinds 种类集合,每个kind内包含多个器件
 * @param {*} s 总预算
 * @param {*} reliabilities 器件可靠性集合
 */
function getResult(kinds, s, reliabilities) {
  // 记录题解
  let ans = -1;

  // 将每个种类内的器件按照可靠性升序
  for (let kind of kinds) {
    kind.sort((a, b) => a.reliability - b.reliability);
  }

  // 将所有器件的可靠性集合,变为数组,并将可靠性升序
  const maybe = [...reliabilities].sort((a, b) => a - b);

  // 在maybe中二分选取可能的题解
  let low = 0;
  let high = maybe.length - 1;

  while (low <= high) {
    const mid = (low + high) >> 1;

    // 如果maybe[mid]可靠性可以保证所有种类器件都能选到,且价格之和小于s
    if (check(kinds, maybe[mid], s)) {
      // 则maybe[mid]可靠性就是一个可能解
      ans = maybe[mid];
      // 继续尝试更优解,即找更大的可靠性
      low = mid + 1;
    } else {
      // 否则,说明可靠性选高了,我们应该继续尝试更低的可靠性
      high = mid - 1;
    }
  }

  return ans;
}

function check(kinds, reliability, s) {
  let sumPrice = 0;

  for (let kind of kinds) {
    // 注意kind内的器件已经按照可靠性升序了
    // 我们需要在该kind种类内找到一个可靠性>=maxReliability的器件
    let idx = binarySearch(kind, reliability);

    // 如果idx<0,则说明idx是一个插入位置
    if (idx < 0) idx = -idx - 1;

    // 如果idx==kind.size()则说明kind内所有器件的可靠性都低于maxReliability,因此此kind器件选取不到,可以直接返回false
    if (idx == kind.length) return false;

    // 否则,选取对应idx位置的器件,并计入价格到总价
    sumPrice += kind[idx].price;
  }

  // 如果最终总价小于总预算s,则此maxReliability可行
  return sumPrice <= s;
}

function binarySearch(kind, target) {
  let low = 0;
  let high = kind.length - 1;

  while (low <= high) {
    const mid = (low + high) >> 1;
    const device = kind[mid];

    if (device.reliability > target) {
      high = mid - 1;
    } else if (device.reliability < target) {
      low = mid + 1;
    } else {
      return mid;
    }
  }

  return -low - 1;
}

Python算法源码

class Device:
    def __init__(self, reliability, price):
        self.reliability = reliability
        self.price = price


# 输入获取
s, n = map(int, input().split())  # s总预算, n器件的种类数
total = int(input())  # 之后输入total行具体器件的数据
arr = [list(map(int, input().split())) for _ in range(total)]

reliabilities = set()  # 收集器件的可靠性

kinds = [[] for _ in range(n)]  # 各种类集合
for ty, reliability, price in arr:
    kinds[int(ty)].append(Device(reliability, price))
    reliabilities.add(reliability)


def binarySearch(kind, target):
    low = 0
    high = len(kind) - 1

    while low <= high:
        mid = (low + high) >> 1
        device = kind[mid]

        if device.reliability > target:
            high = mid - 1
        elif device.reliability < target:
            low = mid + 1
        else:
            return mid

    return -low - 1;


def check(reliability):
    sumPrice = 0

    for kind in kinds:
        # 注意kind内的器件已经按照可靠性升序了
        # 我们需要在该kind种类内找到一个可靠性>=maxReliability的器件
        idx = binarySearch(kind, reliability)

        # 如果idx<0,则说明idx是一个插入位置
        if idx < 0:
            idx = -idx - 1

        # 如果idx==kind.size()则说明kind内所有器件的可靠性都低于maxReliability,因此此kind器件选取不到,可以直接返回false
        if idx == len(kind):
            return False

        # 否则,选取对应idx位置的器件,并计入价格到总价
        sumPrice += kind[idx].price

    # 如果最终总价小于总预算s,则此maxReliability可行
    return sumPrice <= s


# 算法入口
def getResult():
    # ans记录题解
    ans = -1

    # 将每个种类内的器件按照可靠性升序
    for kind in kinds:
        kind.sort(key=lambda x: x.reliability)

    # 将所有器件的可靠性集合,变为数组
    maybe = list(reliabilities)
    maybe.sort()

    # 二分选取可能的最大可靠性maybe
    low = 0
    high = len(maybe) - 1
    while low <= high:
        mid = (low + high) >> 1

        # 如果maybe[mid]可靠性可以保证所有种类器件都能选到,且价格之和小于s
        if check(maybe[mid]):
            # 则maybe[mid]可靠性就是一个可能解
            ans = maybe[mid]
            # 继续尝试更优解,即找更大的可靠性
            low = mid + 1
        else:
            # 否则,说明可靠性选高了,我们应该继续尝试更低的可靠性
            high = mid - 1

    return ans


# 算法调用
print(getResult())

免责声明:

1、IT资源小站为非营利性网站,全站所有资料仅供网友个人学习使用,禁止商用
2、本站所有文档、视频、书籍等资料均由网友分享,本站只负责收集不承担任何技术及版权问题
3、如本帖侵犯到任何版权问题,请立即告知本站,本站将及时予与删除下载链接并致以最深的歉意
4、本帖部分内容转载自其它媒体,但并不代表本站赞同其观点和对其真实性负责
5、一经注册为本站会员,一律视为同意网站规定,本站管理员及版主有权禁止违规用户
6、其他单位或个人使用、转载或引用本文时必须同时征得该帖子作者和IT资源小站的同意
7、IT资源小站管理员和版主有权不事先通知发贴者而删除本文

0

评论0

站点公告

没有账号?注册  忘记密码?