[蓝桥杯][2019年第十届省赛真题] 糖果 (IDA*解决重复覆盖问题)

题目链接:糖果

一、题目描述

糖果店的老板一共有M 种口味的糖果出售。为了方便描述,我们将M种口味编号1~M。小明希望能品尝到所有口味的糖果。遗憾的是老板并不单独出售糖果,而是K颗一包整包出售。幸好糖果包装上注明了其中K 颗糖果的口味,所以小明可以在买之前就知道每包内的糖果口味。给定N 包糖果,请你计算小明最少买几包,就可以品尝到所有口味的糖果。

输入
第一行包含三个整数N、M 和K。
接下来N 行每行K 这整数T1,T2,…,TK,代表一包糖果的口味。
1<=N<=100,1<=M<=20,1<=K<=20,1<=Ti<=M。

输出
一个整数表示答案。如果小明无法品尝所有口味,输出 −1。

样例输入
6 5 3
1 1 2
1 2 3
1 1 3
2 3 5
5 4 2
5 1 2

样例输出
2

前言

本题是经典的重复覆盖问题,这类问题是算法竞赛里非常常见的一种题型,一般的重复覆盖问题最优解是为dancing link,不过博主实力不行 ,dancing link 的代码太难调,所以这里就记录下暴力做法的优化。

解题思路

相信从题目描述中不难想出暴力做法,直接从每一行中去寻找还缺少哪种糖果,但是直接查找是会查找的话肯定是会超时的。所以我们需要进行优化,但是我们应该怎么优化呢?在重复覆盖问题里,是可以用IDA* 优化的。

算法介绍

那IDA* 是什么算法?
其实IDA* 是由两部分组成,迭代加深+估价函数。

迭代加深 其实就是在我们深搜的时候,为了防止DFS在一个错误的方向上一直延伸向下查找,而设立每一次搜索对搜索层数的限制,如果答案在更深层,则depth++。

int depth = 0;                       
    while(depth <= m && !dfs(depth,0)) depth ++ ; //IDA*迭代加深  搜索一层情况,不够在慢慢扩散 

估价函数 其实就是我们在深搜时进行的一个可行性剪枝,在这个题目里我们是进行对目标的预估,比如总的层数有5层,如果我们现在搜索到了第三层,现在预估函数给出我们至少还需要3层,则总的层数<现在所在的层数 +还剩的层数,则退出。

bool 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;             //因为i是反的,所以得先将row取反 
        }
    }
    return res;
}

介绍完了算法后,代码里面还有一些细节操作来说明一下,在一个每一行中,我们可以用01来表示有没有糖果,所以我们可以利用二进制来存储,数据范围m<20,所以用int储存1<<m是完全足够的。同时在搜索的时候,可以用lowbit取得最低位的1来优化。而且搜索过程中,我们可以找每列中最少的那一行,比如在案例中,4只有一行出现过,那么它就是必选的,而1在很多行都出现,所以我们的策略直接从最少的开始查找,进一步优化速度。

代码

#include<bits/stdc++.h>
using namespace std;
const int N = 110, M = 1 << 20;
vector<int> col[N];
int Log2[M];
int n,m,k;
int lowbit(int x){return x & -x;}       //返回最低位的1所对应的值
bool 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;                                    //因为i是反的,所以得先将row取反 
        }
    }
    return res;
}
bool dfs(int depth,int state)
{
    if(!depth || h(state) > depth) return state == (1 << m) - 1;         //如果层数搜完了,或者剪枝不够在继续 则返回状态stare 
    
    // 找到选择性最少的一列
    int t = -1;
    for(int i = (1 << m) - 1 - state; i;i -= lowbit(i))            //i这里将原本为 表示1作为有糖果,转化为0有糖果,这样可以让lowbit查找优化 
    {
        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;
        }
        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 ++ ; //IDA*迭代加深  搜索一层情况,不够在慢慢扩散 
    
    if (depth > m) depth = -1;
    cout<<depth<<endl;
    return 0;
}

总结

这篇博客介绍了IDA* 算法和一些二进制操作的运算,总而言之,本题的信息量炸裂,在蓝桥杯里面这题难度还是很高的。在这里插入图片描述

  • 24
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 13
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 13
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

老帅比阿

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值