题目描述:糖果店的老板一共有M种口味的糖果出售。为了方便描述,我们将M种口味编号1-M。
小明希望能品尝到所有口味的糖果,遗憾的是老板并不单独出售糖果,而是K颗一包整包出售;
幸好糖果包装上标注了其中K颗糖果的口味,所以小明可以在买之前就知道每包内的糖果口味。
给定N包糖果,请你计算小明最少买几包,就可以品尝到所有口味的糖果;
输入描述:第一行包含3个整数N, M, K
接下来N行每行包含K个整数,表示一包糖果的口味。
其中,1<=N<=100, 1<=m<=20, 1<=K<=20。
6 5 3
1 1 2
1 2 3
1 1 3
2 3 5
5 4 2
5 1 2
输出描述:
输出一个整数表示答案。如果小明无法品尝所有口味,输出-1
2
思路:状态压缩+动态规划
状态压缩:题目要求凑成所有口味的糖果,我们就需要用一个二进制数来表示已经凑成了多少种糖果。二进制数的第n位为1表示包含该糖果,例如:输入样例中的第一包糖果1,1,2,那么可以用二进制表示为00011;第一包+第二包就是00011 | 00111 = 00111.
动态规划:用dp[i]表示凑成二进制i最少需要多少包。
详细思路见代码解析:
#include <bits/stdc++.h>
using namespace std;
#define int long long
const int N = (1<<20)+10;
int n, m, k;
int dp[N], a[N];
signed main(){
memset(dp, 0x3f3f3f3f, sizeof(dp)); //初始化dp为无穷大,表示目前这种糖果组合无法凑出
cin >> n >> m >> k;
int all = (1<<m)-1; //all可以看做全为 1 的 m 位二进制数
for(int i = 1; i<=n; i++){
for(int j = 1; j<=k; j++){
int t;
cin >> t;
a[i] |= (1<<(t-1)); //a[i]表示第 i 包里面的糖果组合
}
dp[a[i]] = 1; // 凑出第 i 包的糖果组合至少需要 1 包
}
for(int i = 1; i<=n; i++){ //第 i 包糖果
for(int j = 0; j<=all; j++){ //所有可能出现的糖果组合
dp[a[i]|j] = min(dp[a[i]|j], dp[j]+1); //详细推理见下图
}
}
if(dp[all]>n) cout << -1;
else cout << dp[all];
return 0;
}