问题描述
小强在玩一个策略闯关游戏,手上有 m 组英雄,每组有 n 个英雄,每个英雄拥有不同的武力值 t。游戏要求玩家从每组英雄中各选择一个英雄组成英雄组合,并且英雄组合的武力值总和需要等于或超过关卡的要求,才能通关。
系统已经提示小强有 k 种组合方式可以通关(k < n),这让小强得知了自己英雄组合的潜在战斗力。为了合理安排英雄搭配,请你帮助小强估算出所需的武力值范围。
输入格式
- 第一行输入
m(英雄组数)、n(每组英雄的数量)和k(系统提示的可通关英雄组合数)。 - 接下来
m行,每行输入n个英雄的武力值t序列。
输出格式
- 输出一行,表示关卡武力值的估算范围。第一个数字表示最小可能值,第二个数字表示最大可能值。如果最大值与最小值相等,直接输出即可。
测试样例
样例1:
输入:
m = 3, n = 3, k = 1, array = [[3, 1, 2], [2, 3, 4], [3, 6, 4]]
输出:[13, 13]
样例2:
输入:
m = 2, n = 4, k = 2, array = [[5, 8, 6, 9], [7, 4, 3, 5]]
输出:[15, 15]
样例3:
输入:
m = 2, n = 4, k = 1, array = [[5, 8, 6, 9], [7, 4, 3, 5]]
输出:[16, 16]
#include <algorithm> // 包含 sort, reverse
#include <iostream>
#include <numeric> // 包含 accumulate (虽然这里没直接用,但求和相关)
#include <queue> // 包含优先队列
#include <set> // 包含 set 用于记录访问过的状态
#include <vector>
// 定义优先队列中存储的元素类型
// first: 组合的武力值总和 (使用 long long 防止溢出)
// second: 该组合中每个组选择的英雄的索引 (在排序后的数组中的索引)
using State = std::pair<long long, std::vector<int>>;
std::vector<int> solution(int m, int n, int k,
std::vector<std::vector<int>> &array) {
// 检查基本输入有效性
if (m <= 0 || n <= 0 || k <= 0 || array.size() != m) {
return {}; // 返回空 vector 表示无效输入或无法处理的情况
}
for (const auto &group : array) {
if (group.size() != n) {
return {}; // 英雄数量不匹配
}
}
// 1. 对每个组的英雄按武力值降序排序
// 这样 array[i][0] 就是第 i 组武力值最高的英雄
for (int i = 0; i < m; ++i) {
std::sort(array[i].begin(), array[i].end(), std::greater<int>());
}
// 2. 初始化
// 使用最大优先队列(默认)存储 State
std::priority_queue<State> pq;
// 使用 set 存储访问过的索引组合,防止重复计算和无限循环
std::set<std::vector<int>> visited;
// 存储找到的前 k+1 大的总和
std::vector<long long> top_sums;
// 计算初始状态(选择每组最强的英雄)
long long initial_sum = 0;
std::vector<int> initial_indices(m, 0); // 初始索引都为 0
for (int i = 0; i < m; ++i) {
// 检查是否有英雄可选 (虽然前面检查了 n > 0,但以防万一)
if (array[i].empty())
return {};
initial_sum += array[i][0];
}
// 将初始状态推入优先队列并标记为已访问
pq.push({initial_sum, initial_indices});
visited.insert(initial_indices);
// 3. 寻找前 k+1 大的总和
// 我们需要第 k 大和第 k+1 大的总和来确定范围
// 如果总组合数小于 k+1,也要能处理
int count = 0;
while (!pq.empty() && count < k + 1) {
// 取出当前最大和的状态
State current_state = pq.top();
pq.pop();
long long current_sum = current_state.first;
std::vector<int> current_indices = current_state.second;
// 记录这个总和
top_sums.push_back(current_sum);
count++;
// 如果已经找到了 k+1 个,就不需要再扩展了 (除非这是最后一个)
if (count == k + 1) {
break;
}
// 产生邻居状态(尝试将每个组的选择换成下一个较弱的英雄)
for (int i = 0; i < m; ++i) {
// 创建下一个状态的索引
std::vector<int> next_indices = current_indices;
// 检查当前组是否有下一个英雄可选
if (next_indices[i] + 1 < n) {
next_indices[i]++; // 切换到下一个英雄
// 如果这个邻居状态没有访问过
if (visited.find(next_indices) == visited.end()) {
// 计算新状态的总和
// 可以通过当前和减去旧英雄的值,加上新英雄的值来高效计算
long long next_sum = current_sum - array[i][current_indices[i]] +
array[i][next_indices[i]];
// 将新状态加入优先队列和访问集合
pq.push({next_sum, next_indices});
visited.insert(next_indices);
}
// 注意:这里不需要将 next_indices[i]-- 还原,因为 next_indices 是
// current_indices 的副本
}
}
}
// 4. 分析结果并确定范围
// 检查是否找到了足够的组合
if (top_sums.size() < k) {
// 如果找到的组合数少于 k,说明总组合数就少于 k
// 这与题目 "系统已经提示小强有 k 种组合方式可以通关" 矛盾
// 或者说,如果总组合数 T_total < k,那么所有 T_total 种组合都必须通关
// 此时,最小的通关阈值T可以是 T_total 个组合中最小的那个值 S_{T_total}。
// 但题目暗示k < n^m,所以这种情况理论上不应发生,除非输入 k 过大。
// 返回空表示无法满足条件或输入有问题。
return {};
}
long long s_k = top_sums[k - 1]; // 第 k 大的总和 (0-based index)
// Case 1: 恰好找到了 k 个组合 (说明总共就只有 k 个组合, k == n^m)
// 这种情况与题目 k < n^m 矛盾,但代码需要处理
// 或者,更可能的是,我们只需要 k 个就停了,没有找第 k+1 个
if (top_sums.size() == k) {
// 如果只有 k 个组合能形成,并且这 k 个都通关了
// 那么关卡要求的武力值 T 必须 <= s_k
// 为了满足 "恰好 k 个通关",并且没有第 k+1 个组合存在
// 唯一的可能是 T = s_k。
// 题目说了 k < n^m,所以理论上 top_sums.size() 至少是 k+1 (如果 n^m >= k+1)
// 如果因为 n^m = k 而导致 size 为 k,那么最小阈值就是 s_k
// 范围是 [s_k, s_k]
// 注意:由于 long long 可能很大,需要转换回
// int。检查是否溢出是好的实践,但这里假设结果在 int 范围内。
if (s_k > std::numeric_limits<int>::max() ||
s_k < std::numeric_limits<int>::min())
return {}; // 溢出
return {static_cast<int>(s_k), static_cast<int>(s_k)};
}
// Case 2: 找到了至少 k+1 个组合
long long s_k_plus_1 = top_sums[k]; // 第 k+1 大的总和
// 根据推导,关卡要求 T 满足 s_{k+1} < T <= s_k
// 因为 T 是整数,所以最小可能的 T 是 s_{k+1} + 1
// 最大可能的 T 是 s_k
long long min_T_ll = s_k_plus_1 + 1;
long long max_T_ll = s_k;
// 检查是否存在这样的整数 T
if (min_T_ll > max_T_ll) {
// 这种情况发生在 s_k == s_{k+1} 时。
// 这意味着没有整数 T 能恰好让 k 个组合通过,而第 k+1 个不通过。
// 这与题目条件矛盾,返回空表示无解。
return {};
}
// 检查结果是否在 int 范围内
if (min_T_ll > std::numeric_limits<int>::max() ||
min_T_ll < std::numeric_limits<int>::min() ||
max_T_ll > std::numeric_limits<int>::max() ||
max_T_ll < std::numeric_limits<int>::min()) {
return {}; // 结果超出 int 范围
}
// 返回结果范围
return {static_cast<int>(min_T_ll), static_cast<int>(max_T_ll)};
}
int main() {
// Add your test cases here
std::vector<std::vector<int>> array = {{3, 1, 2}, {2, 3, 4}, {3, 6, 4}};
std::vector<int> result = solution(3, 3, 1, array);
std::cout << (result == std::vector<int>{13, 13}) << std::endl;
return 0;
}