倪文迪陪你学蓝桥杯2021寒假每日一题:1.31日(2019省赛A组第9题)

2021年寒假每日一题,2017~2019年的省赛真题。本文内容由倪文迪(华东理工大学计算机系软件192班)和罗勇军老师提供。每日一题,关注蓝桥杯专栏: https://blog.csdn.net/weixin_43914593/category_10721247.html

2019省赛A组第9题“糖果” ,题目链接:
http://oj.ecustacm.cn/problem.php?id=1460

1、题目描述


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


2、题解

2.1 暴力

  先想想暴力法:对 n n n包糖果做任意组合,找到其中一种组合,能覆盖所有口味,并且需要的糖果包数量最少。 n n n包糖果的组合共有 2 n 2^n 2n种,而 n n n=100,显然会严重超时。

2.2 状态压缩DP

  本题很容易想到用DP来做。
  (1)定义状态 d p [ i ] dp[i] dp[i],表示得到口味组合 i i i所需要的最少糖果包数量。
  (2)状态如何转移?往口味组合 i i i中加入一包糖果,设得到新的口味组合 j j j,说明从 i i i j j j需要糖果包数量 d p [ i ] + 1 dp[i]+1 dp[i]+1。若原来的 d p [ j ] 大 于 d p [ i ] + 1 dp[j]大于dp[i]+1 dp[j]dp[i]+1,说明原来得到 j j j的方法不如现在的方法,更新 d p [ j ] = d p [ i ] + 1 dp[j]=dp[i]+1 dp[j]=dp[i]+1
  这里关键的问题是如何表示口味组合。学过状态压缩DP的队员都知道,像题目中只有小规模的m=20这种应用,用状态压缩的二进制数来表示口味是很简单的。
  例如一包里面有3颗糖果,分别是“2,3,5”三种口味,用二进制数“10110”表示,二进制数的每一位表示一种口味。
  状态压缩DP的原理和扩展学习,参考博文https://blog.csdn.net/weixin_43914593/article/details/106432695
  注意博文中这句话:“这样概况状态压缩DP的思想:集合的状态(子集或排列),如果用二进制表示状态,并用二进制的位运算来遍历和操作,又简单又快。当然,由于集合问题是NP问题,所以状态压缩DP的复杂度仍然是指数的,只能用于小规模问题的求解。”
  下面给出C++代码,代码中详细解释了状态压缩的表示和DP转移。总复杂度是 O ( n 2 m ) O(n2^m) O(n2m),当n=100,m=20时,循环约1亿次,提交到OJ,勉强AC。

//改写自:User: 20192131006  Time:550 ms  Memory:6216 kb
#include<bits/stdc++.h>
using namespace std;
int dp[1<<20]; //dp[v]:得到口味为v时需要的最少糖果包数量
int ST[100];   //ST[i]:第i包糖果的口味

int main() {
    
    
    int n,m,k; cin>>n>>m>>k;

    int tot=(1<<m)-1;  //tot:二进制是m个1,表示所有m种口味

    memset(dp, -1, sizeof dp);

    for (int i=0; i<n; i++){
    
    
        int st=0;
        for (int j=0; j<k; j++){
    
    
            int x;
            cin>>x;
            st|=(1<<x-1);  //状态压缩
        }
        dp[st]=1;   //dp[v]:得到口味为v时需要的最少糖果包数量
        ST[i]=st;   //ST[i]:第i包糖果的口味
    }
    for (int i=0; i<=tot; i++)  //遍历所有口味组合
        if (dp[i]!=-1)          //已存在得到口味i的最少糖果包数量
            for (int j=0; j<n; j++)  {
    
      //检查给定的n包糖果
                int st=ST[j];
                if (dp[i|st]==-1 || dp[i|st]>dp[i]+1)  //状态转移
                    dp[i|st]=dp[i]+1;
            }
    cout << dp[tot];  //得到所有口味tot的最少糖果包数量
    return 0;
}

  本来想加上Python代码的,但是总所周知,Python的for循环很慢,本题有1亿次循环,还是算了。

猜你喜欢

转载自blog.csdn.net/weixin_43914593/article/details/112979691