算法练习题27---蓝桥杯2020省赛“装饰珠”


前言

蓝桥杯2020省赛,编程题(C++)

一、题目描述

在怪物猎人这一款游戏中,玩家可以通过给装备镶嵌不同的装饰珠来获取 相应的技能,以提升自己的战斗能力。

已知猎人身上一共有 6 件装备,每件装备可能有若干个装饰孔,每个装饰孔有各自的等级,可以镶嵌一颗小于等于自身等级的装饰珠 (也可以选择不镶嵌)。

装饰珠有 M 种,编号 1 至 M,分别对应 M 种技能,第 i 种装饰珠的等级为 iLi,只能镶嵌在等级大于等于Li 的装饰孔中。

对第 i 种技能来说,当装备相应技能的装饰珠数量达到 Ki 个时,会产生 Wi(Ki) 的价值。镶嵌同类技能的数量越多,产生的价值越大,即 Wi(Ki−1)<Wi(Ki)。但每个技能都有上限 Pi(1≤Pi≤7),当装备的珠子数量超过 Pi 时,只会产生Wi(Pi) 的价值。

对于给定的装备和装饰珠数据,求解如何镶嵌装饰珠,使得 6 件装备能得到的总价值达到最大。

输入描述

输入的第 1 至 6 行,包含 6 件装备的描述。其中第 i 行的第一个整数 Ni 表示第 i 件装备的装饰孔数量。后面紧接着 Ni 个整数,分别表示该装备上每个装饰孔的等级 L (1≤L≤4)。

第 7 行包含一个正整数 M,表示装饰珠 (技能) 种类数量。

第 8 至 M + 7 行,每行描述一种装饰珠 (技能) 的情况。每行的前两个整数 Lj (1≤Lj≤4) 和 Pj (1≤Pi≤7) 分别表示第 j种装饰珠的等级和上限。接下来 Pj 个整数,其中第 k 个数表示该装备中装饰珠数量为 k 时的价值 Wj(k)。

其中,1≤Ni≤50,1≤M≤104,1≤Wj(k)≤104。

输出描述

输出一行包含一个整数,表示能够得到的最大价值。

输入输出样例

示例

输入

1 1
2 1 2
1 1
2 2 2
1 1
1 3
3
1 5 1 2 3 5 8
2 4 2 4 8 15
3 2 5 10

输出

20

样例说明

按照如下方式镶嵌珠子得到最大价值 20,括号内表示镶嵌的装饰珠的种类编号:

1: (1)
2: (1) (2)
3: (1)
4: (2) (2)
5: (1)
6: (2)

4 颗技能 1 装饰珠,4 颗技能 2 装饰珠 W1(4)+W2(4)=5+15=20。

运行限制

  • 最大运行时间:1s
  • 最大运行内存: 256M

二、思路解析

毋庸置疑这是一道非常难的动态规划问题,但仔细去分析可以发现这是一道基于0/1背包问题的动态规划题目。

首先我们要有一个动态规划的思路,我进行了以下数理:

  • 每件装备有对应的等级,等级最大不超过4,那么我们可以以相同的等级去给这些装饰孔进行统计分类,然后按照相同的等级去遍历这些装饰孔
  • 输入数据中,有m行不同的装饰珠,所以这些装饰珠可以理解为0/1背包问题中的物品,控制外层for循环
  • 这里有一点与0/1背包中不同的地方,就是在0/1背包问题中,对于同样重量的物品,也就是相同的物品,个数对价值的影响是线性的,单个价值不会变,而在这道题目中,个数对于价值所产生的影响是没有规律的,因此要存放到一个数组中,对于不同的数量分别遍历
  • 在外层不同装饰珠的控制下,内层要用装饰珠数量和背包容量相结合的方式(这里又是两层循环)
  • 遍历思路:因为装饰孔中只能镶嵌小于等于该等级的装饰珠,因此从最高等级的装饰孔逐级向下遍历,这样保证只要在最高等级范围内的装饰珠永远是有装入的可能性(以此条件才能构建状态转移方程)
  • 题目中有一个比较坑的地方,就是装饰珠的等级有可能会大于所有的装饰孔,这就是一种无法使用的装饰珠

数据结构上:

int dp[a][b] 表示在前a种珠子(包括a)的情况下,装饰孔数量为b时所能产生的最大价值

选取最大值时,比较的是在前a-1种珠子下,与当前加入了a种珠子时对于最大价值的影响,这里还需要一层循环用来控制第a种珠子个数对价值的影响

核心程序:

//现将前kind-1种珠子的价值情况存放到当前开放第kind种珠子后的数组中,方便后续处理
for(int j=1;j<=count;j++)
{
	dp[kind][j]=dp[kind-1][j];
}

//状态转移方程
for (int t = 1; t <= lim[i]; t++)  //对于珠子的每种价值
{
	for (int j = t;j<=count;j++)  //分析背包容量所产生的影响
	{
		dp[kind][j] = max(dp[kind][j], dp[kind - 1][j - t] + value[i][t]);  //比较不放和放两种情况产生的价值
	}
}

另一组测试样例:

输入:

5 1 1 1 1 1
9 1 1 1 1 1 1 1 1 1
7 1 1 1 1 1 1 1
3 1 1 1
7 1 1 1 1 1 1 1
9 1 1 1 1 1 1 1 1 1
15
4 3 48 86 129
3 1 3
1 7 45 84 109 154 175 206 233
1 2 35 42
1 6 7 52 67 116 131 156
1 1 32
3 4 12 15 61 82
1 7 5 36 85 88 124 139 163
1 7 29 32 69 101 150 197 210
1 6 9 50 73 102 141 158
3 2 9 44
1 3 1 39 86
1 2 35 78
1 7 3 50 53 96 107 140 175
3 6 16 63 74 89 92 140

输出:

1195

三、AC代码

#include<bits/stdc++.h>
using namespace std;
int main()
{
    int num[7]={0};  //装备对应的装饰孔数量
    int weapon[7][51]={0};  //装备对应装饰孔的等级
    int count_grade[5]={0};  //统计相同等级下的个数
    int grade[10005]={0};  //装饰珠等级
    int lim[10005]={0};  //装饰珠价值上限个数
    int value[10005][8]={0};  //装饰珠的价值
    int m;  //装饰珠的种类
    int total=0;
    int dp[10001][300]={0};  //dp背包
    for(int i=1;i<=6;i++)
    {
        cin>>num[i];
        for(int j=1;j<=num[i];j++)
        {
            cin>>weapon[i][j];  //等级
            count_grade[weapon[i][j]]++;
            total++;
        }
    }
    cin>>m;
    for(int i=1;i<=m;i++)
    {
        cin>>grade[i];
        cin>>lim[i];
        for(int j=1;j<=lim[i];j++)
        {
            cin>>value[i][j];  //在第i种珠子下,个数为j所对应的价值
        }
    }
    int count=0;   //截止目前开放的孔数
    int kind=0;  //已经遍历的珠子种类
    
    //等级从高等级向低等级遍历
    for(int level=4;level>0;level--)
    {
        if(count_grade[level]!=0)
        {
            count+=count_grade[level];  //孔数累加
        }
        if(count>0)
        {
            for(int i=1;i<=m;i++)  //遍历m种珠子
            {            
                if(grade[i]==level)  //在该等级下发现新的珠子
                {
    
                    kind++;
                    //现将前kind-1种珠子的价值情况存放到当前开放第kind种珠子后的数组中,方便后续处理
                    for(int j=1;j<=count;j++)
                    {
                        dp[kind][j]=dp[kind-1][j];
                    }
                    
                    //状态转移方程
                    for (int t = 1; t <= lim[i]; t++)  //对于珠子的每种价值
                    {
                        for (int j = t;j<=count;j++)  //分析背包容量所产生的影响
                        {
                            dp[kind][j] = max(dp[kind][j], dp[kind - 1][j - t] + value[i][t]);  //比较不放和放两种情况产生的价值
                        }
                    }
                }
            }
        }
    }
    
    int temp=0;
    for(int i=1;i<=total;i++)
    {
        if(temp<dp[kind][i])
        {
            temp=dp[kind][i];
        }
    }
    cout<<temp<<endl;
    return 0;
}

参考大佬博客:蓝桥杯真题——装饰珠_Hey XIN的博客-CSDN博客_蓝桥杯装饰珠

感谢阅读!也欢迎大家关注小白博主,多多鼓励一下!

  • 7
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

杨大熊的代码世界

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

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

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

打赏作者

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

抵扣说明:

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

余额充值