题解:荣耀的羁绊
问题描述
小 hua 和他的朋友在玩一款 MOBA 类手游,游戏中有五个位置(对抗路、发育路、中路、打野、游走),每局开始前需要禁用英雄并选择英雄。每个英雄可以被推荐为一个或多个分路,玩家只能选择自己会玩的英雄,并且每个英雄只能走被推荐的分路。
给定以下信息:
- $ n $ 个英雄及其推荐分路。
- 五个玩家会玩的英雄集合。
- $ Q $ 种不同的禁用英雄集合。
要求计算对于每种禁用英雄集合,有多少种合法的阵容选择。
合法阵容需满足以下条件:
- 所有玩家选择的英雄互不相同。
- 阵容中不存在被禁用的英雄。
- 每个玩家使用自己会玩的英雄。
- 每个英雄走被推荐的分路。
- 每个分路恰好有一个英雄。
解题思路
1. 状态压缩与预处理
由于 $ n \leq 15 $,我们可以用二进制位来表示英雄的选择集合。例如,若 $ n = 10 $,则集合 $ {1, 3, 5} $ 可以表示为二进制数 $ 000010101_2 $。
-
预处理所有可能的英雄选择方案
对于每个英雄选择方案 $ S $(即英雄集合),我们可以通过暴力搜索计算出其对应的合法阵容数量。具体步骤如下:- 枚举五个玩家分别选择的英雄组合。
- 确保每个英雄只走被推荐的分路。
- 确保每个分路恰好有一个英雄。
- 记录该方案的合法阵容数量。
-
优化搜索过程
使用排列枚举和剪枝技巧减少无效枚举。例如:- 如果某个英雄无法满足当前分路需求,则立即剪枝。
- 利用位运算加速判断英雄是否属于某个玩家的可用集合。
2. 查询时直接枚举
对于每种禁用英雄集合 $ B $,我们需要从所有英雄中排除被禁用的英雄,得到剩余可用英雄集合 $ A $。然后,枚举 $ A $ 的所有子集,累加这些子集对应的预处理结果。
- 复杂度分析
- 预处理阶段:共有 $ 2^n $ 种英雄选择方案,每次计算的复杂度为 $ O(5!) $(排列枚举),总复杂度为 $ O(2^n \cdot 5!) $。
- 查询阶段:对于每个禁用集合 $ B $,枚举剩余英雄集合 $ A $ 的子集,复杂度为 $ O(Q \cdot 2^n) $。
3. 状态压缩 DP(进一步优化)
为了进一步优化预处理阶段,可以采用小型的状态压缩 DP。具体方法如下:
- 按照英雄数量分层枚举,逐步构建合法阵容。
- 在每一层中,记录当前已选英雄集合和分路分配情况。
- 利用位运算快速判断是否满足分路限制。
通过这种方法,可以显著减少无效枚举的数量,从而提高效率。
实现代码
以下是基于上述思路的实现代码:
#include <bits/stdc++.h>
using namespace std;const int mod = 1e9 + 7;
constexpr int N = 15;// 快速幂函数
long long qpow(long long a, long long b) {long long res = 1;while (b) {if (b & 1) res = res * a % mod;a = a * a % mod;b >>= 1;}return res;
}void solve() {int n, q;cin >> n >> q;// 输入五个玩家会玩的英雄集合vector<int> player_heroes(5, 0);for (int i = 0; i < 5; i++) {int cnt;cin >> cnt;while (cnt--) {int x;cin >> x;player_heroes[i] |= (1 << (x - 1));}}// 输入英雄的分路推荐矩阵vector<vector<int>> hero_routes(n, vector<int>(5, 0));for (int i = 0; i < n; i++) {for (int j = 0; j < 5; j++) {cin >> hero_routes[i][j];}}// 预处理所有英雄选择方案的合法阵容数量vector<int> dp(1 << n, 0);vector<int> p(5);iota(p.begin(), p.end(), 0);do {// 枚举当前分路分配方案vector<int> v(5, 0);for (int i = 0; i < 5; i++) {v[i] = player_heroes[p[i]];}// 搜索所有可能的英雄选择function<void(int, int)> dfs = [&](int u, int mask) {if (u == 5) {dp[mask]++;return;}for (int i = 0; i < n; i++) {if ((mask >> i) & 1) continue; // 已选过的英雄跳过if (((v[u] >> i) & 1) && hero_routes[i][p[u]]) { // 英雄可选且符合分路dfs(u + 1, mask | (1 << i));}}};dfs(0, 0);} while (next_permutation(p.begin(), p.end()));// 动态规划优化for (int j = 0; j < n; j++) {for (int i = 1; i < (1 << n); i++) {if ((i >> j) & 1) {dp[i] += dp[i ^ (1 << j)];}}}// 处理查询while (q--) {int ban_count;cin >> ban_count;int ban_mask = 0;while (ban_count--) {int x;cin >> x;ban_mask |= (1 << (x - 1));}int available_mask = ((1 << n) - 1) ^ ban_mask;cout << dp[available_mask] << '\n';}
}int main() {ios::sync_with_stdio(false);cin.tie(nullptr);int T;cin >> T;while (T--) solve();
}