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

Posted 老帅比阿

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了[蓝桥杯][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* 算法和一些二进制操作的运算,总而言之,本题的信息量炸裂,在蓝桥杯里面这题难度还是很高的。

以上是关于[蓝桥杯][2019年第十届省赛真题] 糖果 (IDA*解决重复覆盖问题)的主要内容,如果未能解决你的问题,请参考以下文章

2019年第十届蓝桥杯 - 省赛 - Java研究生组 - A. 立方和

《蓝桥杯真题》:2019年单片机省赛(第十届)

《蓝桥杯真题》:2019年单片机省赛(第十届)

《蓝桥杯真题》:2019年单片机省赛(第十届)

2019年第十届蓝桥杯 - 省赛 - C/C++大学C组 - B. 矩形切割

2019年第十届C/C++ A组蓝桥杯省赛第四题:迷宫