1125. 最小的必要团队

题目链接:1125. 最小的必要团队

方法:状态压缩 + 0,1背包

解题思路

  • 状态压缩:由题目可知,\(req_skills\) 存放的是所有技能的集合,每个人会其中的某几项。若将 \(req_skills\) 中的每个技能对应二进制上的一位 \(1\),可以得到一个标志 \(target\),那么每个人也将得到一个表示其所会技能的标志 \(mask\)
  • 问题:从 \(people\) 中选择一些人(物品),使得其 \(mask\) 的并集为 \(target\),同时使得选择的人数最少;
    • \(dp[i][j]\) 表示从前 \(i\) 个人中选,集合的并集为 \(j\) 的所有选择方式;
    • 其数值表示所有选择方式中人数的最小值,由于题目要给出选择方案,那么将其数值变为存储一个二进制数,某位为 \(1\) 表示选择了该人;
    • 此时 \(dp\) 数组应该初始化为最大值,即所有人都选;
    • 即 0,1 背包变形,可以进行相应优化。

背包问题详解

代码

class Solution {
public:
    vector<int> smallestSufficientTeam(vector<string>& req_skills, vector<vector<string>>& people) {
        unordered_map<string, int> idx;
        int m = req_skills.size(), target = (1 << m) - 1;
        for (int i = 0; i < m; i ++ ) idx[req_skills[i]] = i; // 记录技能下标
        int n = people.size(); // 人数
        vector<int> skill(n); // skill[i]表示i会的技能,记录每个人状态压缩后的结果
        for (int i = 0; i < n; i ++ ) {
            int mask = 0;
            for (int j = 0; j < people[i].size(); j ++ ) {
                int bit = idx[people[i][j]];
                mask |= (1 << bit);
            }
            skill[i] = mask;
        }
        long long dp[target + 1]; fill(dp, dp + target + 1, (1LL << n) - 1); // 0,1背包优化为1维,初始化为每个人都选,即最大值
        dp[0] = 0; // 并集为0,所有人都不选
        for (int i = 1; i <= n; i ++ ) {
            for (int j = target; j >= 0; j -- ) {
                auto t1 = dp[j]; // 不选 i
                auto t2 = dp[j & ~skill[i - 1]] | (1L << (i - 1)); // 选 i
                dp[j] = __builtin_popcountll(t1) < __builtin_popcountll(t2) ? t1 : t2; // 取其中人数最小的为答案
            }
        }
        auto res = dp[target]; // 最终的二进制数,将其转化为所选的人的集合
        int cnt = 0;
        vector<int> ans;
        while (res) {
            if (res & 1) ans.push_back(cnt);
            res >>= 1;
            cnt ++ ;
        }
        return ans;
    }
};

复杂度分析

时间复杂度:\(O(T+n2^m)\),其中 \(T\)\(people\) 中的字符串的个数之和,\(n\)\(people\) 的长度,\(m\)\(reqSkills\) 的长度。
空间复杂度:\(O(n + m + 2^m)\)

posted @ 2023-04-18 00:03  lixycc  阅读(62)  评论(0)    收藏  举报