题意
给定\(N\)个上锁的宝箱。有一个商店卖\(M\)个钥匙,每个钥匙的价格是\(a_i\),并且可以解锁\(b_i\)个宝箱,分别是\(c_{i, 1}, c_{i, 2}, \dots, c_{i, b_{i}}\)。
每个钥匙可以购买之后可以使用任意多次。
问:要解锁所有宝箱需要花费多少钱?
题目链接:https://atcoder.jp/contests/abc142/tasks/abc142_e
数据范围
\(1 \leq N \leq 12\)
\(1 \leq M \leq 10^3\)
思路
观察到\(N\)的范围很小,因此考虑状压DP。
令\(f(i, j)\)表示使用前\(i\)个钥匙,解锁状态为\(j\)的宝箱,需要的最少花费。其中\(j\)为一个\(n\)位二进制数,如:\(10001\)表示解锁第\(1\)个和第\(5\)个宝箱。
我们二重循环枚举使用前\(i\)个钥匙,以及用第\(i\)个钥匙之前的状态\(j\)。
- 如果不使用第\(i\)个钥匙,那么\(f(i, j) = \min \{f(i - 1, j), f(i, j)\}\)。
- 如果使用第\(i\)个钥匙,我们令\(c_i\)表示第\(i\)个钥匙可以解锁哪些钥匙。那么当前可以解锁的状态为\(k = j | c_i\),则\(f(i, k) = \min \{f(i, k), f(i - 1, j)+a_i\}\)
\(c_i\)可以提前预处理。
代码
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 1010, M = (1 << 12) + 10;
int n, m;
int a[N], c[N], f[N][M];
int main()
{
scanf("%d%d", &n, &m);
for(int i = 1; i <= m; i ++) {
int x;
scanf("%d%d", &a[i], &x);
int tmp = 0;
for(int j = 1; j <= x; j ++) {
int t;
scanf("%d", &t);
tmp |= 1 << (t - 1);
}
c[i] = tmp;
}
memset(f, 0x3f, sizeof f);
f[0][0] = 0;
for(int i = 1; i <= m; i ++) {
for(int j = 0; j < 1 << n; j ++) {
f[i][j] = min(f[i][j], f[i - 1][j]);
int t = c[i] | j;
f[i][t] = min(f[i][t], f[i - 1][j] + a[i]);
}
}
if(f[m][(1 << n) - 1] == 0x3f3f3f3f) printf("-1\n");
else printf("%d\n", f[m][(1 << n) - 1]);
return 0;
}