0
点赞
收藏
分享

微信扫一扫

经典问题——重复覆盖问题


糖果店的老板一共有 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

题解

样例解释

经典问题——重复覆盖问题_#include


重复覆盖问题最少选几行使得每一列至少存在一个1

Dancing links(十字链表,跳舞表),最快做法,但很难写。

我们使用优雅的暴力搜索
搜索顺序
枚举选择一个没有被选过的列,枚举它选择哪一行。

优化

  1. 迭代加深(枚举1行够不够,不够再枚举两行……)
  2. 找选择最少的列
  3. 可行性剪枝 估价函数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)


举报

相关推荐

0 条评论