📜  门|门 CS 1997 |第 51 题(1)

📅  最后修改于: 2023-12-03 14:58:35.015000             🧑  作者: Mango

题目描述

在一条直路上有 n 个房间,这些房间被依次标号为 0 到 n - 1,第 i 个房间里有 ni 个钥匙,钥匙的编号分别为 0 到 ni - 1。每个钥匙都有一个价值,标号为 i 的钥匙价值为 Ci,j。(0 ≤ i < n, 0 ≤ j < ni)

开始每个房间里都有一个钥匙,你现在处在第 0 个房间,你每次可以花费 1 的代价打开一个房间,这样你就可以进入这个房间。当你进入一个房间之后,你可以用房间里的钥匙打开这个房间需要的钥匙,然后将剩余的钥匙留在这个房间里。你需要打开第 n - 1 个房间,并使得你进入这个房间时钥匙的总价值最大。

输入

第一行一个整数 n,代表房间的总数(2 ≤ n ≤ 200)

接下来的 n 行,第 i 行的第一个数是 ni(1 ≤ ni ≤ 20),接下来 ni 个数表示在第 i 个房间里面的钥匙的价值 Ci,j(0 ≤ Ci,j ≤ 10,000)

输出

输出一个整数,表示所求的最大总价值。

样例

输入

4
2 1 2
2 2 1
2 4 4
2 4 4

输出

11
程序员解题要点

通过题目描述可以了解到本题是一道典型的动态规划问题,可以通过类似于背包问题的思路去解决。

我们可以先定义一个二维数组 dp,dp[i][j] 表示当到达第 i 个房间时,手中拥有钥匙集合为 j 时的最大钥匙价值。

那么在转移时,我们可以枚举上一个房间 i-1 中留下的钥匙集合q,再枚举当前房间 i 中的钥匙集合s,得到一个新的钥匙集合 p = q ∪ s,如果 dp[i-1][q]存在,说明之前已经到达过 i-1 房间并且拥有钥匙集合为 q,并且可以从 i-1 号房间到达 i 号房间,那么此时能够拿到的最大价值是 dp[i-1][q] + sum(Ci, j),其中 sum(Ci, j) 是指集合 s 中所有元素的价值和。

最后,dp[n-1]数组中的最大值即为我们要求的答案。

对于实现,我们需要用到一些数据结构,比如位运算和集合运算。在读入房间和钥匙价值部分,可以采用 vector<vector> 的数据结构,也可以用邻接表来存储每个房间的信息,然后在实现 dp 过程时使用 bitset<> 或 set<> 来存储每个集合。

代码示例

下面是本题的参考代码,供大家参考:

#include <iostream>
#include <vector>
#include <bitset>
#include <algorithm>
using namespace std;
const int INF = 1e9;
#define pb push_back
typedef vector<int> vi;
typedef vector<vi> vvi;
vvi keylist;
vi sumval;
set<int> S;//集合不支持 '&' 操作
const int MAXN = 205;
bitset<1 << 20> f[MAXN];
int main() {
    ios::sync_with_stdio(0); cin.tie(0); cout.tie(0);
    int n;
    cin >> n;
    keylist.resize(n); sumval.resize(n);
    for (int i = 0; i < n; i++) {
        int ki; cin >> ki;
        while (ki--) {
            int ui; cin >> ui;
            keylist[i].pb(ui);
            sumval[i] += ui;
        }
    }
    f[0][1] = 1;//0号房间初始含钥匙0号
    for (int i = 1; i < n; i++) {
        for (int j = 0; j < f[i].size(); j++) {
            if (!f[i][j]) continue;
            S.clear();
            S.insert(j & 1);
            for (int k = 0; k < keylist[i].size(); k++)
                if (j & (1 << keylist[i][k]))
                    S.insert(keylist[i][k] + 1);
            for (int sk : S)
                f[i].set(j | (1 << sk));
        }
    }
    int ans = 0;
    for (int i = 0; i < (1 << (n - 1)); i++) {
        if (f[n - 1][i])
            ans = max(ans, sumval[n - 1] - sumval[__builtin_popcount(i ^ ((1 << (n - 1)) - 1))]);
    }
    cout << ans << '\n';
    return 0;
}