糖果店的老板一共有 M 种口味的糖果出售。
为了方便描述,我们将 M 种口味编号 1∼M。
小明希望能品尝到所有口味的糖果。
遗憾的是老板并不单独出售糖果,而是 K 颗一包整包出售。
幸好糖果包装上注明了其中 K 颗糖果的口味,所以小明可以在买之前就知道每包内的糖果口味。
给定 N 包糖果,请你计算小明最少买几包,就可以品尝到所有口味的糖果。
输入格式
第一行包含三个整数 N,M,K。
接下来 N 行每行 K 这整数 T1,T2,⋅⋅⋅,TK,代表一包糖果的口味。
输出格式
一个整数表示答案。
如果小明无法品尝所有口味,输出 −1。
数据范围
1≤N≤100,
1≤M,K≤20,
1≤Ti≤M
输入样例:
6 5 3
1 1 2
1 2 3
1 1 3
2 3 5
5 4 2
5 1 2
输出样例:
2
题解
样例解释
重复覆盖问题(最少选几行使得每一列至少存在一个1)
Dancing links(十字链表,跳舞表),最快做法,但很难写。
我们使用优雅的暴力搜索
搜索顺序:
枚举选择一个没有被选过的列,枚举它选择哪一行。
优化:
- 迭代加深(枚举1行够不够,不够再枚举两行……)
- 找选择最少的列
- 可行性剪枝 估价函数h():至需要再选多少行。
1和3优化叫IDA
lowbit优化,我们用二进制表示物品的选或没选,初始的时候1表示已经选了,0表示没有选择。但lowbit可以快速求出末尾1的位置(lowbit(x)返回的是x末尾1代表的2^pos值,但log2[lowbit(x)]j就表示末尾1的位置pos),所以我们对其反转一下,0表示已经选了,1表示没有选择,这样就可以利用lowbit快速找到没有选择物品的列了。
//糖果
//大致顺序:先枚举可选择数最少的一列,然后再在这一列中枚举选择哪一行
//这样的时间复杂度应该是最低的,
#include<iostream>
#include<cstring>
#include<cstdio>
#include<vector>
using namespace std;
const int N=110, M=1<<20;
vector<int > col[N];//用[j][j]]
int n,m,k;
int log2[M];//预处理,方便计算 log2(2的n次方)
int lowbit(int x)
{
return x& -x;
}
int h(int state)
{
//编写估价函数,看这时的state最少需要用几行来完成
int res=0;
//求最小方案数时,假设选择了某一列,则等价于选择了这一列的全部方案数
for(int i=(1<<m)-1-state;i;i-=lowbit(i))
{
int c=log2[lowbit(i)];//i返回最后一位1,通过log2直接映射为最后一位1的位置
res++;
for(auto row:col[c])
{
i=i&~row; //row表示哪一列有1,每次选择一种方案,等价于将这种方案对应的位变为0,通过&操作实现
}
}
return res;
}
// depth表示层数,state用于维护选择糖果过程中已经选择了哪些口味
bool dfs(int depth,int state)
{
if(!depth||h(state)>depth)
{
//若可选择的方案为0或者最小需要选择的方案数都小于当前可选的方案数的话,则判断是否合法
//判断方法:看state是否全为1
return state==(1<<m)-1;// (1<<m)-1表示m位全是一, 即2^m-1
}
//接下来找可选择数最少的一列
int t=-1;//t是指向选择数最少的那一列的指针
//(全1 - state): 得到没选的为1、选择为0的状态i,然后用lowbit()取出最后一位1的值。
for (int i = (1 << m) - 1 - state; i; i -= lowbit(i))
{
int c = log2[lowbit(i)];
if (t == -1 || col[t].size() > col[c].size())
t = c;
}
//接下来枚举选择哪一行
for(auto row: col[t])
{
if(dfs(depth-1,state|row)) return true;
}
return false;
}
int main()
{
scanf("%d%d%d",&n,&m,&k);
//预处理log2
for(int i=0;i<m;i++)
{
log2[1<<i]=i;
}
for(int i=0;i<n;i++)
{
int state=0;
//将该包糖果所包含的糖果对应的位数置为1
for(int j=0;j<k;j++)
{
int c;
cin>>c;
// state将c-1列置为1
state=state|(1<<(c-1));
}
//找出这包糖果 哪个位置可以填成1,将该列对应的col+1
for(int j=0;j<m;j++)
{
if(state>>j&1)//若第j位有1
{
col[j].push_back(state);
}
}
}
int depth=0;// 枚举至少需要选择的行数
while(!dfs(depth,0)&&depth<=m) depth++;
if(depth>m) depth=-1;
cout<<depth<<endl;
return 0;
}
import sys
sys.setrecursionlimit(10**9)
IA = lambda:map(int, input().split())
M = 1 << 20
n, m, k = IA()
col = [[] for i in range(m + 10)]
log2 = [0 for i in range((1 << m) + 10)]
for i in range(m):
log2[1 << i] = i
for i in range(n):
state = 0
x = list(IA())
for c in x:
state = state | (1 << (c - 1))
for j in range(m):
if state >> j & 1:
col[j].append(state)
def h(state):
global m
res = 0
i = (1 << m) - 1 - state
while i:
c = log2[i & (-i)]
for row in col[c]:
i = i & ~ row
i -= i & (-i)
return res
def dfs(depth, state):
global m
if depth == 0 or h(state) > depth:
return state == (1 << m) - 1
t = -1
i = (1 << m) - 1 - state
while i:
c = log2[i & (-i)]
if t == -1 or len(col[t]) > len(col[c]):
t = c
i -= i & (-i)
for row in col[t]:
if dfs(depth-1, state | row): return True
return False
depth = 0
while dfs(depth, 0) == False and depth <= m:
depth += 1
if depth > m:
depth = -1
print(depth)