Get Everything(状压DP)

题意

给定\(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;
}
posted @ 2022-07-03 20:55  pbc的成长之路  阅读(61)  评论(0)    收藏  举报