Problem - 4546 (hdu.edu.cn)
最近,小明出了一些ACM编程题,决定在HDOJ举行一场公开赛。
假设题目的数量一共是n道,这些题目的难度被评级为一个不超过1000的非负整数,并且一场比赛至少需要一个题,而这场比赛的难度,就是所有题目的难度之和,同时,我们认为一场比赛与本场题目的顺序无关,而且题目也不会重复。
显而易见,很容易得到如下信息:
经过简单估算,小明发现总方案数几乎是一个天文数字!
为了简化问题,现在小明只想知道在所有的方案里面第m小的方案,它的比赛难度是多少呢?
输入数据的第一行为一个整数T(1 <= T <= 20),表示有T组测试数据。
每组测试数据:
对于每组测试数据,输出一行"Case #c: ans"(不包含引号),ans 表示要求的第m小的比赛难度,输入数据保证存在第m小的方案,具体参见样例。
输入 | 2 5 6 1 1 1 1 1 5 25 1 2 3 4 5 |
输出 | Case #1: 2 Case #2: 11 |
说明 | 无 |
本题其实就是让我们求解: 全组合中"第m小"组合(按组合之和排大小)。
比如nums = [1,2,3],则全部组合如下:
其中:
由于本题数量级较大,因此如果暴力地求出全组合,然后按组合之和排序,求出第m小的组合,肯定会超时。
如果我们将nums进行升序,比如nums = [1, 2, 3, 4],那么第1小的组合肯定是 [1]。
而第1小组合[1],可以看成是基于一个空组合[],加了一个nums最小元素1产生的。
第2小组合此时就有了两种来源:
对比可得第2小组合是[2]。
此时,我们有了三个最小组合,分别是[],[1],[2],如果在为每个组合标出下一个合入元素的话,则可得信息如下:
此时根据”当前组合“、”将被被合入的元素“,我们可以得出将要产生的"新组合":
可以对比出,将要产生的新组合中[3]和[1,2]是本轮最小的,我们可以任选一个作为整体第3小。
假设我们选择[3]作为第3小组合,则组合信息变化如下:
此时再算出新组合信息,如下:
可以得出本轮最小,整体第4小组合是[1,2]。
此时组合信息变化如下:
由于nums只有[1,2,3,4]元素,没有5,因此对于组合 [] 来说,已经没有新元素可以合入了,因此我们只需要处理剩下的组合即可
可以得出本轮最小,整体第5小组合是[1,3]。
此时组合信息变化如下:
按上面逻辑,第m轮得出的最小组合就是第m小的组合。
具体代码实现中,我们可以使用优先队列来找出每轮最小的:”将要产生的新组合“。
而加入优先队列的组合模型设计如下:
根据这两个信息可以得出”将要产生的新组合“之和 = curSum + nums[nextIdx]
而对于一个组合模型,其”将要产生的新组合“之和越小,则优先级越高。
关于JS中优先队列的实现可以参考:LeetCode - 1705 吃苹果的最大数目_leetcode - 1705 吃苹果的最 数 _伏城之外的博客-csdn博客_伏城之外的博客-CSDN博客
const rl = require("readline").createInterface({ input: process.stdin });
var iter = rl[Symbol.asyncIterator]();
const readline = async () => (await iter.next()).value;
// 输入处理
void (async function () {
const t = parseInt(await readline());
const ans = [];
for (let i = 1; i <= t; i++) {
const [n, m] = (await readline()).split(" ").map(Number);
const nums = (await readline()).split(" ").map(Number);
ans.push(`Case #${i}: ${getResult(n, m, nums)}`);
}
for (let an of ans) console.log(an);
})();
// 算法入口
function getResult(n, m, nums) {
nums.sort((a, b) => a - b);
// 对于一个组合模型,其”将要产生的新组合“之和越小,则优先级越高
// curSum + nums[nextIdx] 为 ”将要产生的新组合“之和
const pq = new PriorityQueue(
(a, b) => a.curSum + nums[a.nextIdx] - (b.curSum + nums[b.nextIdx])
);
// 空组合的和为0, 将要加入的新元素是nums[0], 即索引0的元素,其将要产生的新组合之和为 0 + nums[0]
let c = new CombineModel(0, 0);
for (let i = 1; i < m; i++) {
// c是当前最小组合模型,最小的组合模型指的是将要产生的新组合之和在对应轮次中最小
// 如果当前组合模型c还有可合入的下一个元素,即c.nextIdx + 1 < n, 则说明可以基于当前组合模型产生一个新组合
if (c.nextIdx + 1 < n) {
// 基于当前组合模型产生的新组合,也是本轮最小的组合,即第 i 小组合
pq.offer(new CombineModel(c.curSum + nums[c.nextIdx], c.nextIdx + 1));
// 当前组合需要更新nextIdx后,重新加入优先队列
c.nextIdx += 1;
pq.offer(c);
}
// 取出优先队列中最小组合(注意这里的最小,指的是基于当前组合,将要产生的新组合之和最小)
c = pq.poll();
}
// 经过m-1轮后, 优先队列中存储的最最小组合模型 得出的 新组合是 第m小组合
return c.curSum + nums[c.nextIdx];
}
// 组合模型
class CombineModel {
constructor(curSum, nextIdx) {
this.curSum = curSum; // 当前组合之和
this.nextIdx = nextIdx; // 将要被加入当前组合的新元素索引位置
}
}
// 基于堆实现优先队列
class PriorityQueue {
constructor(cpr) {
this.queue = [];
this.size = 0;
this.cpr = cpr;
}
swap(i, j) {
let tmp = this.queue[i];
this.queue[i] = this.queue[j];
this.queue[j] = tmp;
}
// 上浮
swim() {
let ch = this.queue.length - 1;
while (ch !== 0) {
let fa = Math.floor((ch - 1) / 2);
const ch_node = this.queue[ch];
const fa_node = this.queue[fa];
if (this.cpr(ch_node, fa_node) < 0) {
this.swap(ch, fa);
ch = fa;
} else {
break;
}
}
}
// 下沉
sink() {
let fa = 0;
while (true) {
let ch_left = 2 * fa + 1;
let ch_right = 2 * fa + 2;
let ch_max;
let ch_max_node;
const fa_node = this.queue[fa];
const ch_left_node = this.queue[ch_left];
const ch_right_node = this.queue[ch_right];
if (ch_left_node && ch_right_node) {
// 注意这里应该要>=0,因为左边优先级高
if (this.cpr(ch_left_node, ch_right_node) <= 0) {
ch_max = ch_left;
ch_max_node = ch_left_node;
} else {
ch_max = ch_right;
ch_max_node = ch_right_node;
}
} else if (ch_left_node && !ch_right_node) {
ch_max = ch_left;
ch_max_node = ch_left_node;
} else if (!ch_left_node && ch_right_node) {
ch_max = ch_right;
ch_max_node = ch_right_node;
} else {
break;
}
// 注意这里应该要>0,因为父优先级高
if (this.cpr(ch_max_node, fa_node) < 0) {
this.swap(ch_max, fa);
fa = ch_max;
} else {
break;
}
}
}
// 向优先队列中加入元素
offer(ele) {
this.queue.push(ele);
this.size++;
this.swim();
}
// 取出最高优先级元素
poll() {
this.swap(0, this.queue.length - 1);
this.size--;
const ans = this.queue.pop();
this.sink();
return ans;
}
}
import java.util.ArrayList;
import java.util.Arrays;
import java.util.PriorityQueue;
import java.util.Scanner;
public class Main {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
ArrayList ans = new ArrayList<>();
int t = sc.nextInt();
for (int i = 1; i <= t; i++) {
int n = sc.nextInt();
int m = sc.nextInt();
int[] nums = new int[n];
for (int j = 0; j < n; j++) {
nums[j] = sc.nextInt();
}
ans.add("Case #" + i + ": " + getResult(n, m, nums));
}
for (String an : ans) {
System.out.println(an);
}
}
static class CombineModel {
int curSum; // 当前组合之和
int nextIdx; // 将要被加入当前组合的新元素索引位置
public CombineModel(int curSum, int nextIdx) {
this.curSum = curSum;
this.nextIdx = nextIdx;
}
}
public static int getResult(int n, int m, int[] nums) {
Arrays.sort(nums);
// 对于一个组合模型,其”将要产生的新组合“之和越小,则优先级越高
// curSum + nums[nextIdx] 为 ”将要产生的新组合“之和
PriorityQueue pq =
new PriorityQueue<>((a, b) -> a.curSum + nums[a.nextIdx] - (b.curSum + nums[b.nextIdx]));
// 空组合的和为0, 将要加入的新元素是nums[0], 即索引0的元素,其将要产生的新组合之和为 0 + nums[0]
CombineModel c = new CombineModel(0, 0);
for (int i = 1; i < m; i++) {
// c是当前最小组合模型,最小的组合模型指的是将要产生的新组合之和在对应轮次中最小
// 如果当前组合模型c还有可合入的下一个元素,即c.nextIdx + 1 < n, 则说明可以基于当前组合模型产生一个新组合
if (c.nextIdx + 1 < n) {
// 基于当前组合模型产生的新组合,也是本轮最小的组合,即第 i 小组合
pq.offer(new CombineModel(c.curSum + nums[c.nextIdx], c.nextIdx + 1));
// 当前组合需要更新nextIdx后,重新加入优先队列
c.nextIdx += 1;
pq.offer(c);
}
// 取出优先队列中最小组合(注意这里的最小,指的是基于当前组合,将要产生的新组合之和最小)
c = pq.poll();
}
// 经过m-1轮后, 优先队列中存储的最最小组合模型 得出的 新组合是 第m小组合
return c.curSum + nums[c.nextIdx];
}
}
import queue
def getResult(n, m, nums):
nums.sort()
pq = queue.PriorityQueue()
class CombineModel:
def __init__(self, curSum, nextIdx):
self.curSum = curSum # 当前组合之和
self.nextIdx = nextIdx # 将要被加入当前组合的新元素索引位置
def __lt__(self, other):
# 对于一个组合模型,其”将要产生的新组合“之和越小,则优先级越高
# curSum + nums[nextIdx] 为 ”将要产生的新组合“之和
return self.curSum + nums[self.nextIdx] < (other.curSum + nums[other.nextIdx])
# 空组合的和为0, 将要加入的新元素是nums[0], 即索引0的元素,其将要产生的新组合之和为 0 + nums[0]
c = CombineModel(0, 0)
for _ in range(1, m):
# c是当前最小组合模型,最小的组合模型指的是将要产生的新组合之和在对应轮次中最小
# 如果当前组合模型c还有可合入的下一个元素,即c.nextIdx + 1 < n, 则说明可以基于当前组合模型产生一个新组合
if c.nextIdx + 1 < n:
# 基于当前组合模型产生的新组合,也是本轮最小的组合,即第 i 小组合
pq.put(CombineModel(c.curSum + nums[c.nextIdx], c.nextIdx + 1))
# 当前组合需要更新nextIdx后,重新加入优先队列
c.nextIdx += 1
pq.put(c)
# 取出优先队列中最小组合(注意这里的最小,指的是基于当前组合,将要产生的新组合之和最小)
c = pq.get()
# 经过m-1轮后, 优先队列中存储的最最小组合模型 得出的 新组合是 第m小组合
return c.curSum + nums[c.nextIdx]
t = int(input())
ans = []
for i in range(t):
n, m = map(int, input().split())
nums = list(map(int, input().split()))
ans.append(f"Case #{i+1}: {getResult(n, m, nums)}")
for an in ans:
print(an)