1009 荣耀的羁绊

题解:荣耀的羁绊

问题描述

小 hua 和他的朋友在玩一款 MOBA 类手游,游戏中有五个位置(对抗路、发育路、中路、打野、游走),每局开始前需要禁用英雄并选择英雄。每个英雄可以被推荐为一个或多个分路,玩家只能选择自己会玩的英雄,并且每个英雄只能走被推荐的分路。

给定以下信息:

  • $ n $ 个英雄及其推荐分路。
  • 五个玩家会玩的英雄集合。
  • $ Q $ 种不同的禁用英雄集合。

要求计算对于每种禁用英雄集合,有多少种合法的阵容选择。

合法阵容需满足以下条件:

  1. 所有玩家选择的英雄互不相同。
  2. 阵容中不存在被禁用的英雄。
  3. 每个玩家使用自己会玩的英雄。
  4. 每个英雄走被推荐的分路。
  5. 每个分路恰好有一个英雄。

解题思路

1. 状态压缩与预处理

由于 $ n \leq 15 $,我们可以用二进制位来表示英雄的选择集合。例如,若 $ n = 10 $,则集合 $ {1, 3, 5} $ 可以表示为二进制数 $ 000010101_2 $。

  • 预处理所有可能的英雄选择方案
    对于每个英雄选择方案 $ S $(即英雄集合),我们可以通过暴力搜索计算出其对应的合法阵容数量。具体步骤如下:

    1. 枚举五个玩家分别选择的英雄组合。
    2. 确保每个英雄只走被推荐的分路。
    3. 确保每个分路恰好有一个英雄。
    4. 记录该方案的合法阵容数量。
  • 优化搜索过程
    使用排列枚举和剪枝技巧减少无效枚举。例如:

    • 如果某个英雄无法满足当前分路需求,则立即剪枝。
    • 利用位运算加速判断英雄是否属于某个玩家的可用集合。
2. 查询时直接枚举

对于每种禁用英雄集合 $ B $,我们需要从所有英雄中排除被禁用的英雄,得到剩余可用英雄集合 $ A $。然后,枚举 $ A $ 的所有子集,累加这些子集对应的预处理结果。

  • 复杂度分析
    • 预处理阶段:共有 $ 2^n $ 种英雄选择方案,每次计算的复杂度为 $ O(5!) $(排列枚举),总复杂度为 $ O(2^n \cdot 5!) $。
    • 查询阶段:对于每个禁用集合 $ B $,枚举剩余英雄集合 $ A $ 的子集,复杂度为 $ O(Q \cdot 2^n) $。
3. 状态压缩 DP(进一步优化)

为了进一步优化预处理阶段,可以采用小型的状态压缩 DP。具体方法如下:

  1. 按照英雄数量分层枚举,逐步构建合法阵容。
  2. 在每一层中,记录当前已选英雄集合和分路分配情况。
  3. 利用位运算快速判断是否满足分路限制。

通过这种方法,可以显著减少无效枚举的数量,从而提高效率。


实现代码

以下是基于上述思路的实现代码:

#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();
}
posted @ 2025-03-31 00:00  archer2333  阅读(53)  评论(0)    收藏  举报