重复覆盖问题

一、介绍:

重复覆盖问题:如下例题对应的图,选取几行,使得每一列都有一个1

精确覆盖问题:如下例题对应的图,选取几行,使得每一列都只有一个1

目前有一个解决这两类问题最快的一个算法:Dancing Links,但是难写难调

二、经典做法:DFS

每一次找到一个未被覆盖的列,枚举所有包含这一列的行,接着递归下一列

三、优化:

1、迭代加深(首先先看有没有选一行的答案,再看有没有选两行的答案......)

2、每次找到选择最少的列(若选了这一行,剩余需要覆盖的列数最少)

3、可行性剪枝(至少选多少行和剩余的行数比较)

4、位运算(同费解的开关)

1和3一起被称作IDA*

例题:

                                                               糖果

糖果店的老板一共有 M 种口味的糖果出售。

为了方便描述,我们将 M 种口味编号 1∼M。

小明希望能品尝到所有口味的糖果。

遗憾的是老板并不单独出售糖果,而是 K 颗一包整包出售。

幸好糖果包装上注明了其中 K 颗糖果的口味,所以小明可以在买之前就知道每包内的糖果口味。

给定 N 包糖果,请你计算小明最少买几包,就可以品尝到所有口味的糖果。

输入格式

第一行包含三个整数 N,M,K。

接下来 N行每行 K这整数 T1,T2,⋅⋅⋅,T,代表一包糖果的口味。

输出格式

一个整数表示答案。

如果小明无法品尝所有口味,输出 −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<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<vector>

using namespace std;

const int N=110,M=1<<20;

int n,m,k;
vector<int> col[N];//记录该列可以选哪一行
int log2[M];

int lowbit(int x)
{
   return x&-x;
}

int h(int state)//估价函数,判断最少需要几行
{
    int res=0;
    for(int i=(1<<m)-1-state;i;i-=lowbit(i))
    {
        int c=log2[lowbit(i)];
        res++;
        for(auto row:col[c]) i&=~row;
    }
    return res;
}

bool dfs(int depth,int state)
{
    if(!depth||h(state)>depth)//判断是否每一列都被覆盖了
      return state==(1<<m)-1;
    
    //找到选择性最少的一列
    int t=-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()
{
    cin>>n>>m>>k;

    for(int i=0;i<m;i++) log2[1<<i]=i;
    for(int i=0;i<n;i++)
    {
        int state=0;
        for(int j=0;j<k;j++)
        {
            int c;
            cin>>c;
            state|=1<<c-1;//标志第c-1位为1
        }

        for(int j=0;j<m;j++)//将当前状态记录进各列的可选择项中
        {
            if(state>>j&1)
              col[j].push_back(state);
        }
    }
      int depth=0;
        while(depth<=m&&!dfs(depth,0)) depth++;

        if(depth>m) depth=-1;
        cout<<depth<<endl;

        return 0;
}

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值