【洛谷】P8687 [蓝桥杯 2019 省 A] 糖果 的题解
题目传送门
题目大意
有 m m m 种口味,每次 k k k 颗一袋出售,给你 n n n 包均为 k k k 颗的糖果,求最少买几袋可以吃到所有口味的糖果。
思路
核心:
-
状态压缩二进制存数。
-
枚举尝试拿糖果。
-
动态规划得出最后结果。
首先考虑数据存放,最直接的肯定是二维数组,bool a[i][j]
,第
i
i
i 包糖果是否含有第
j
j
j 种糖果。
很明显,这样放空间占用大,而且数组遍历浪费时间。
因为最大的糖果种数也就 20 20 20,且每包糖果里每种糖果只有有或者没有两种状态。
所以可以使用二进制来存放,这样存放的好处就是我现在所拥有的糖果种类就可以表示为两数按位或。
思考到这里以后再想,怎么保证得到的结果是最小呢,那就只有枚举了。
在枚举尝试每一包的糖果的时候,通过不断刷新来得到最后结果。
想到这一步之后问题也就解决了,结果自然就是 dp[(1<<m)-1]
了,
m
m
m 种糖果,(1<<m)-1
就是
m
m
m 个数位都是
1
1
1 所表示的十进制数。
代码
#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cmath>
#include <cctype>
#include <climits>
#include <algorithm>
#include <map>
#include <queue>
#include <vector>
#include <ctime>
#include <string>
#include <cstring>
#define lowbit(x) x & (-x)
#define endl "\n"
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
namespace fastIO {
inline int read() {
register int x = 0, f = 1;
register char c = getchar();
while (c < '0' || c > '9') {
if(c == '-') f = -1;
c = getchar();
}
while (c >= '0' && c <= '9') x = x * 10 + c - '0', c = getchar();
return x * f;
}
inline void write(int x) {
if(x < 0) putchar('-'), x = -x;
if(x > 9) write(x / 10);
putchar(x % 10 + '0');
return;
}
}
using namespace fastIO;
int n, m, k, x;
int a[115], dp[1 << 21];
int main() {
//freopen(".in","r",stdin);
//freopen(".out","w",stdout);
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
cin >> n >>m >>k;
for(int i = 1; i <= n; i ++) {
for(int j = 1; j <= k; j ++) {
cin >> x;
a[i] |= 1 << (x - 1);
}
}
memset(dp, 127, sizeof(dp));
dp[0] = 0;
for(int i = 1; i <= n; i ++) {
for(int j = 0; j < (1 << m); j ++) {
if(dp[j] > n) continue;
dp[j | a[i]] = min(dp[j | a[i]], dp[j] + 1);
}
}
if(dp[(1 << m) - 1] > n) cout << -1;
else cout << dp[(1 << m) - 1];
return 0;
}