📅  最后修改于: 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
下面是本题的参考代码,供大家参考:
#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;
}