AcWing 1243. 糖果(Java版)
原题链接
中等
作者:
上杉
,
2021-04-14 17:36:59
,
所有人可见
,
阅读 439
思路和注意点全在代码注释里,大家沉下心来看肯定能看懂,数论这一章总算结束啦,因为java同学比较少,所以基本每道题后面我都发了代码或者题解,大家冲冲冲
import java.util.*;
public class Main{
static int N;
static int M;
static int K;
static int[] candy;
static int[] log2;
//map key代表第几列,value代表当前列代表的口味在哪些糖包中有
static HashMap<Integer,List<Integer>> map = new HashMap<>();
public static void main(String[] args){
Scanner input = new Scanner(System.in);
N = input.nextInt();//总共有多少种糖包
M = input.nextInt();//总共多少种口味的糖
K = input.nextInt();//每个糖包里面多少颗糖(可能有重复口味)
candy = new int[N];
log2 = new int[1 << M];//log2[i]代表以2为底i的对数是多少,
//log[1] = 0,log[2] = 1,log[4] = 2,log[8] =3 方便后面我们根据数字快速得出是哪种口味的糖
for(int i = 0 ; i < M ; i++){
log2[1<<i] = i;
}
for(int i = 0 ; i < N ; i++){
//原来糖的口味是1 - M,方便位运算,我们替换成 0 - M - 1
int curCandy = 0;
for(int j = 0 ; j < K ; j++){
int m = input.nextInt();//当前糖包有第m-1种口味的糖
//从右往左,第几个位置为1代表当前糖包具有第几种口味的糖
curCandy = curCandy | (1 << (m - 1));
}
candy[i] = curCandy;
//当前糖果的口味都保存在curCandy,还需要记录第几列的糖在哪些糖包中有
for(int j = 0 ; j < M ; j++){
if(((curCandy >> j) & 1) == 1){
//当前糖果包含第j种口味的糖
List<Integer> packages = map.get(j);
if(packages == null){
packages = new ArrayList<>();
map.put(j,packages);
}
packages.add(curCandy);
}
}
}
if(map.size() < M){
//所有糖包包含的口味不足M种,肯定凑不齐
System.out.println(-1);
}else{
int count = 1;//count代表我们当前迭代选几包糖
while(count <= M){
if(dfs(count,0)){
//选count种的方案可行,直接退出循环,因为我们是从小往大枚举包数
//这肯定是能凑出所有糖的最小方案
break;
}else{
//count种不能凑出全部。加一包试试
count++;
}
}
//上面的循环等价于这个最简形式,但是展开比较容易理解
//while(count <= M && !dfs(count,0))count++;
System.out.println(count);
//这里为什么不像y总代码里面需要判断是不是凑不出所有糖呢,别忘了在判断map的size的时候已经知道勒
}
}
//count当前还能选的糖包数量、state当前选的糖果状态
public static boolean dfs(int count,int state){
if(count == 0 || mustNeed(state) > count){
//如果不能再选糖包了,或者至少还需要的数量大于当前剩余的数量
//判断一下当前是不是已经凑齐了所有口味
return (1 << M) - 1 == state;
}
//还没凑齐所有口味,并且还能继续往下选,应该挑出当前还没选的糖果
//剩下的可选方案最少的进行枚举
int minCol = -1;//最少的糖包的列是哪一列
for(int i = (1 << M) - 1 - state ; i > 0 ; i -=lowbit(i)){
int col = log2[lowbit(i)];
//枚举还有多少口味的糖没选
if(minCol == -1 || map.get(col).size() < map.get(minCol).size()){
//map.get(col)获取到包含col口味有多少糖包,选出最少的
minCol = col;
}
}
//minCol是剩下没有选的糖果口味中,可选方案最少的糖果口味
for(int pack : map.get(minCol)){
//枚举下一个糖包
if(dfs(count -1,state | pack)){
return true;
}
}
return false;
}
//当前状态至少还需要多少糖包
public static int mustNeed(int state){
int ans = 0;
for(int i = (1 << M) - 1 - state ; i > 0 ; ){
//当前还没选的口味是i中为1的口味
int col = log2[lowbit(i)];//
List<Integer> packges = map.get(col);
ans++;
//col口味包含的所有糖包,选取当前口味最多还能顺带补齐的最大口味数
for(int pack : packges){
i = i & ~pack;
//pack中为1的代表能补齐的糖的口味,i中为1的代表还需要的口味,所以pack中的1应该
//具有将i中的1消掉的功能,所以就是取反1变为0,0变为1,原来的0无影响,原来的1消去
//i中的i
}
}
return ans;
}
public static int lowbit(int x){
return x & -x;
}
}