一.题目
题目描述
糖果店的老板一共有 M 种口味的糖果出售。
为了方便描述,我们将 M 种口味编号 1∼M。
小明希望能品尝到所有口味的糖果。
遗憾的是老板并不单独出售糖果,而是 K 颗一包整包出售。
幸好糖果包装上注明了其中 K 颗糖果的口味,所以小明可以在买之前就知道每包内的糖果口味。
给定 N 包糖果,请你计算小明最少买几包,就可以品尝到所有口味的糖果。
输入格式
第一行包含三个整数 N,M,K。
接下来 N 行每行 K 个整数 T 1 , T 2 , ⋅ ⋅ ⋅ , T K T_1,T_2,⋅⋅⋅,T_K T1,T2,⋅⋅⋅,TK,代表一包糖果的口味。
输出格式
一个整数表示答案。
如果小明无法品尝所有口味,输出 −1。
数据范围
1 ≤ N ≤ 100,
1 ≤ M , K ≤ 20,
1 ≤ T i T_i Ti ≤ M
输入样例
6 5 3
1 1 2
1 2 3
1 1 3
2 3 5
5 4 2
5 1 2
输出样例
2
二.解释
看完题目,首先想到的是直接暴力,我们有 N 包糖果,每包糖果都有选和不选两种状态,写循环显然不可能,那用递归,每重递归处理某包糖果的两种情况。N 包就有 N 重递归,包含大量重复计算,复杂度太高,只能解部分样例。
在判断每包糖果的选与不选的时候,都是基于之前已经处理的情况来决定:
1.当前面的的某种选择情况已经包含这包糖果的所有口味,自然不用选择;
2.当前面的的某种选择情况没有包含这包糖果的所有口味:
设之前已经包含的所有口味为集合 A,当前糖果所有口味为集合 B,这是我们只需要比较之前包含**(A U B)**口味的糖果包数量和集合 A 选择的糖果包数量 + 1的较小值。
此时我们可以用DP的方法来做,对于每种口味,我们用一个位来表示选(1)与不选(0):
设有 DP 数组,对于下表 i,i 的二进制位的前 N 位表示 N 包糖果的状态;
则有DP[i]:口味包含情况为 i 所需的最小糖果包数量。
三.代码
1.暴力递归:
#include <iostream>
#include <unordered_map>
#include <algorithm>
#include <set>using namespace std;const int MaxN = 110;int InN, InM, InK, Res = 0x3f3f3f3f; //糖果包总数,口味总数,每包的口味数
unordered_map<int, int> Map[MaxN]; //记录已经包含的口味void Get(int InI, int InC, unordered_map<int, int> InH)
{if (InH.size() == InM){Res = min(Res, InC);return;}if (InI > InN){if (InH.size() == InM){Res = min(Res, InC);}return;}//不选Get(InI + 1, InC, InH);//选unordered_map<int, int> Tmp = InH;InH.insert(Map[InI].begin(), Map[InI].end()); //把这包糖果的口味加到集合Get(InI + 1, InC + 1, InH);//回溯InH = Tmp;
}int main()
{cin >> InN >> InM >> InK;int a;for (int i = 1; i <= InN; i++){for (int j = 1; j <= InK; j++) { scanf("%d", &a); Map[i][a]++; }}unordered_map<int, int> InH;Get(1, 0, InH);cout << Res;return 0;
}
2.DP:
#include <iostream>
#include <unordered_map>
#include <algorithm>
#include <cstring>
#include <set>using namespace std;const int MaxN = 110;int InN, InM, InK, Res = 0x3f3f3f3f; //糖果包总数,口味总数,每包的口味数
int Ns[MaxN]; //记录每包糖果包含的口味
int DP[1 << 21]; //最多20种口味,最少需要20位。多开一个保险,记录包含口味的最小包数int main()
{cin >> InN >> InM >> InK;int a;for (int i = 1; i <= InN; i++){//使用按位或(|),记录口味for (int j = 1; j <= InK; j++) { scanf("%d", &a); Ns[i] |= 1 << (a - 1); }}memset(DP, 0x3f, sizeof DP);DP[0] = 0;for (int i = 1; i <= InN; i++){for (int j = 0; j <= 1 << InM; j++){//大于N买不到所有口味,跳过if (DP[j] > InN) continue;//已经有j口味,或上Ns[i]表示加上这包糖果的口味DP[j | Ns[i]] = min(DP[j | Ns[i]], DP[j] + 1);}}//以5种口味为例,1 << 5为100000,减一后为11111,表示物种口味全选//当包含所有口味的包数大于N的时候返回-1,否则直接返回结果if (DP[(1 << InM) - 1] > InN) { cout << -1; }else { cout << DP[(1 << InM) - 1]; }return 0;
}