bluecode-小强的英雄组合通关策略

30 阅读6分钟

问题描述

小强在玩一个策略闯关游戏,手上有 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;
}