状态压缩+动态规划(蓝桥杯2019 java A组 糖果问题)

本文介绍了如何运用动态规划(DP)和二进制状态压缩来解决一个关于购买糖果的问题。小明希望尝试所有口味的糖果,而糖果店老板按包出售,每包内含不同口味。通过建立DP数组并使用二进制状态表示已尝试的糖果种类,作者展示了如何计算最少购买多少包糖果才能尝遍所有口味。文章还给出了具体的Java代码实现,并在最后进行了总结。
摘要由CSDN通过智能技术生成

前言

看到这个标题,说明我还是无知了,算法小白报道。
首先之所以会有这个问题是因为,这样一道题目

糖果 时间限制: 1.0s 内存限制: 256.0MB 本题总分:25分

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

【输入格式】   第一行包含三个整数 N、M 和 K。接下来 N 行,每行 K 个整数 T1, T2, · · · ,
TK,代表一包糖果的口味。

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

【样例输入】

6 5 3  
1 1 2  
1 2 3  
1 1 3  
2 3 5  
5 4 2  
5 1 2  

【样例输出】

2  

【评测用例规模与约定】

对于 30% 的评测用例,1 ≤ N ≤ 20 。
对于所有评测样例,1 ≤ N ≤ 100,1 ≤ M ≤ 20,1 ≤ K ≤ 20,1 ≤ Ti ≤ M 。

思路

当时呢,我的第一个想法是类似于一个启发式的一个算法,使用“支配”的想法,想去解决这个问题,但是这个状态不好表示,写出来之后应该是一个递归,不过复杂度的话和dp应该不会差太多,但是那个不好搞。

Dp 思想

那么到这里的话,我们自然就能先联想到背包问题,想到dp。
题目的要求自然就是,说,想要吃到所以糖果的包数嘛(最小包数)。
所以很容易想到一个dp数组 dp[i][j] 在i包下,j 状态下(这个状态是指现在吃到了多少种糖果)。
然后这个 d[i][j] = value 按照这个意思这个value 就是i嘛,这样自然就直接实现了将维,那么此时我们构建的dp是指这样的dp[i]=j 表示在i状态下,需要j包糖果。
在这里插入图片描述

DP方程

我们先来说说这个dp的策略
如果 当前的状态(例如当前表示吃到了4种) 上一种状态是吃到了三种,然后是拿了1包
这里就看情况了,
如果,当前的这个状态在我们原来的dp数组里面保存的状态是没有的,那么自然我们就直接
在上一个状态的基础上拿下这一包就行了
如果有,并且在这种情况下是拿了3包,那么显然此时的这个状态对应的包数也是应该是上一个状态拿到的包数+1

具体的看代码
在这里插入图片描述

如何保存状态

二进制状态保存

前面我们假设了这么多,问题来了我们这个状态怎么表示呀?
这里就是传说当中的状态压缩了,如何直接把一组状态变成一个数字呢?

这里就需要使用到神奇的二进制了。
怎么做呢,例如这组数据
1 1 2
由于我们有5种口味嘛,所以可以这样
我们的二进制是这样的格式 00000
第一个数字过来变成了这样
00001
第二个数字过来,我们 | 取或运算所以还是
00001
接下来第三个
00011

此时我们再转化为十进制于是得到了 3
此时这个三就是表示了我们状态

6 5 3    状态压缩
		二进制  十进制
1 1 2   00011	3
1 2 3	00111	7
1 1 3	00101	5


编码

接下来编码解决问题

class Main{
    private static int N;
    private static int M;
    private static int K;
    static boolean[][] vis = new boolean[105][22];
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        N = sc.nextInt();// 多少包糖
        M = sc.nextInt();// 几种口味
        K = sc.nextInt();// 每包糖多少口味
        int[] w = new int[N+1];
        int[] dp=new int[1<<20];
        for (int i = 1; i <= N; i++) {
            for (int j = 0; j < K; j++) {
                int k = sc.nextInt();
                w[i] |=(1<<k-1) ;
                //存储每一包的状态,但是这个第一个数第一个状态不能改变,所以需要把k-1
                
                //这个的牛逼之处就是把一串状态数组,转化二进制(运算),之后变回十进制表示为一个数字表示状态
            }
        }
        Arrays.fill(dp, -1);//初始化-1,
        dp[0]=0;
        //dp的方程含义是下一个状态
        for(int i = 1;i<=N;i++)
            
            for(int s = 0;s < (1 << M);s++) {
                if (dp[s] == -1) continue;
                int nst = s | w[i];//下一个状态,比上一个状态多吃到一种糖果的状态(多吃一包)
                if (dp[nst] == -1 || dp[nst] > dp[s] + 1) dp[nst] = dp[s] + 1;
                //如果没有状态那么自然-1,如果有这个状态,并且这个状态不如上一个状态多吃一个那么就舍弃,直接上一个状态+1
            }
        System.out.println(dp[(1<<M)]);

    }

}

总结

我是菜鸡…

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

Huterox

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

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

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

打赏作者

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

抵扣说明:

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

余额充值