前言
看到这个标题,说明我还是无知了,算法小白报道。 首先之所以会有这个问题是因为,这样一道题目
糖果 时间限制: 1.0s 内存限制: 256.0MB 本题总分:25分
【问题描述】 ??糖果店的老板一共有 M 种口味的糖果出售。为了方便描述,我们将 M 种口味编号 1 ~ M。 ??小明希望能品尝到所有口味的糖果。遗憾的是老板并不单独出售糖果,而是 K 颗一包整包出售。 ??幸好糖果包装上注明了其中 K 颗糖果的口味,所以小明可以在买之前就知道每包内的糖果口味。 ??给定 N 包糖果,请你计算小明最少买几包,就可以品尝到所有口味的糖果。
【输入格式】 ??第一行包含三个整数 N、M 和 K。接下来 N 行,每行 K 个整数 T1, T2, · · · , TK,代表一包糖果的口味。
【输出格式】 一个整数表示答案。如果小明无法品尝所有口味,输出 ?1。
【样例输入】
6 5 3
1 1 2
1 2 3
1 1 3
2 3 5
5 4 2
5 1 2
【样例输出】
2
【评测用例规模与约定】
对于 30% 的评测用例,1 ≤ N ≤ 20 。 对于所有评测样例,1 ≤ N ≤ 100,1 ≤ M ≤ 20,1 ≤ K ≤ 20,1 ≤ Ti ≤ M 。
思路
当时呢,我的第一个想法是类似于一个启发式的一个算法,使用“支配”的想法,想去解决这个问题,但是这个状态不好表示,写出来之后应该是一个递归,不过复杂度的话和dp应该不会差太多,但是那个不好搞。
Dp 思想
那么到这里的话,我们自然就能先联想到背包问题,想到dp。 题目的要求自然就是,说,想要吃到所以糖果的包数嘛(最小包数)。 所以很容易想到一个dp数组 dp[i][j] 在i包下,j 状态下(这个状态是指现在吃到了多少种糖果)。 然后这个 d[i][j] = value 按照这个意思这个value 就是i嘛,这样自然就直接实现了将维,那么此时我们构建的dp是指这样的dp[i]=j 表示在i状态下,需要j包糖果。
DP方程
我们先来说说这个dp的策略 如果 当前的状态(例如当前表示吃到了4种) 上一种状态是吃到了三种,然后是拿了1包 这里就看情况了, 如果,当前的这个状态在我们原来的dp数组里面保存的状态是没有的,那么自然我们就直接 在上一个状态的基础上拿下这一包就行了 如果有,并且在这种情况下是拿了3包,那么显然此时的这个状态对应的包数也是应该是上一个状态拿到的包数+1
具体的看代码
如何保存状态
二进制状态保存
前面我们假设了这么多,问题来了我们这个状态怎么表示呀? 这里就是传说当中的状态压缩了,如何直接把一组状态变成一个数字呢?
这里就需要使用到神奇的二进制了。 怎么做呢,例如这组数据 1 1 2 由于我们有5种口味嘛,所以可以这样 我们的二进制是这样的格式 00000 第一个数字过来变成了这样 00001 第二个数字过来,我们 | 取或运算所以还是 00001 接下来第三个 00011
此时我们再转化为十进制于是得到了 3 此时这个三就是表示了我们状态
6 5 3 状态压缩
二进制 十进制
1 1 2 00011 3
1 2 3 00111 7
1 1 3 00101 5
编码
接下来编码解决问题
class Main{
private static int N;
private static int M;
private static int K;
static boolean[][] vis = new boolean[105][22];
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
N = sc.nextInt();
M = sc.nextInt();
K = sc.nextInt();
int[] w = new int[N+1];
int[] dp=new int[1<<20];
for (int i = 1; i <= N; i++) {
for (int j = 0; j < K; j++) {
int k = sc.nextInt();
w[i] |=(1<<k-1) ;
}
}
Arrays.fill(dp, -1);
dp[0]=0;
for(int i = 1;i<=N;i++)
for(int s = 0;s < (1 << M);s++) {
if (dp[s] == -1) continue;
int nst = s | w[i];
if (dp[nst] == -1 || dp[nst] > dp[s] + 1) dp[nst] = dp[s] + 1;
}
System.out.println(dp[(1<<M)]);
}
}
总结
我是菜鸡…
|