ZOJ 3769 —— Diablo III(背包,DP)

本文针对一个具体的背包问题进行深入解析,通过分析特定装备配置问题,介绍如何利用动态规划解决复杂限制条件下的最大值问题。文章详细阐述了问题背景、解决思路及代码实现。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

题目:http://acm.zju.edu.cn/onlinejudge/showProblem.do?problemCode=3769

额,题目有点长的样子。

大概意思就是说人身上有13个地方可以装备道具,其中两个注意点,一个是fingers,这说明有两根手指可以装备。。。另一个是Two-Handed这种装备会同时占据Weapon和Shield两种道具的装备位置,同一位置只能装备一个道具,不同位置不会影响。

每种道具都会有damage和toughness两个参数,题目要问的是,从中选出一些装备,那么人物的damage值和toughness就是所选装备对应的和,在保证toughness不小于m的情况下,求最大的damage,如果无法达到toughness,就输出-1。


这个背景还是挺经典的背包问题,只是套上一些限制。

如果按照装备的类型分类,最多就13类,先不管前面说的两个注意点,如果这13类之间是没有影响的,dp[i][j]来表示取到第i 类物品,总toughness是j 时候,最优伤害值。

那么对于第i 类物品,假设当前考虑到其中的第k件物品,两个参数分别是d和t,那么枚举前一类物品能够达到的所有状态dp[i-1][j],可以推出新的状态dp[i][j+t] = max(dp[i][j+t], dp[i][j]+d)

而在这个问题中,因为我们只需要达到m,所以对于j+t超过m情况,可以简单记录为m。


然后建立在这种思路的基础上,我们再来考虑那两个条件:

1、对于两个手指的,无论是只装备一根手指,还是装备了两只,都用手指这一类来表示,那么,所有手指装备本身当作只装备一根手指,装备两只的你就两两枚举下吧;

2、对于Weapon和Shield两种道具以及Two-Handed,我们还是把它们当成一种来处理,首先各自肯定当成一件物品,然后就是枚举Weapon和Shield的搭配了;

这样处理之后,按照我们的递推式,同类物品之间是不会有冲突的,所以以上处理过后,题目的限制条件就不见了。。。


然后我们就可以快乐地AC了~~

PS:因为扫物品名称列表的时候,几个特殊处理的物品都是在后面,所以具体到实现的时候,我是从后往前推的。。。

#include<cstdio>
#include<cstring>
#include<vector>
#include<algorithm>
using namespace std;
#define pb push_back
char name[20][20]={"Head","Shoulder","Neck","Torso","Hand","Wrist","Waist","Legs","Feet","Finger","Shield","Weapon","Two-Handed"};
int t, n, m;
struct Goods{
    int dmg, tou;
}g;
int getnum(char* s){
    for(int i=0; i<13; i++) if(strcmp(s, name[i])==0)   return i;
}
vector<Goods> V[13];//用vector来存储同类物品
int dp[13][50001], vd, vt;
int main(){
    int i, j, k;
    char s[20];
    scanf("%d", &t);
    while(t--){
        scanf("%d %d", &n, &m);
        for(i=0; i<13; i++) V[i].clear();
        while(n--){
            scanf("%s %d %d", s, &i, &j);
            k = getnum(s);
            V[k].pb((Goods){i,j});
            if(k==11 || k==10){//如果发现Weapon和Shield的,也算到Two-Handed里面去
                V[12].pb((Goods){i,j});
            }
        }
        //枚举Weapon和Shield两两组合
        for(i=0; i<V[11].size(); i++){
            for(j=0; j<V[10].size(); j++){
                V[12].pb((Goods){V[11][i].dmg+V[10][j].dmg, V[11][i].tou+V[10][j].tou});
            }
        }
        //把存放Shield的清空,用来记录finger的物品,Weapon的清不清空无所谓
        V[10].clear();
        for(i=0; i<V[9].size(); i++){
            V[10].pb(V[9][i]);
            //枚举finger物品的两两组合
            for(j=i+1; j<V[9].size(); j++){
                V[10].pb((Goods){V[9][i].dmg+V[9][j].dmg, V[9][i].tou+V[9][j].tou});
            }
        }
        V[9].clear();//这个清空,因为finger的情况都存到V[10]里面去了
        memset(dp,-1,sizeof(dp));
        dp[11][0]=0;
        for(i=0; i<V[12].size(); i++){
            g = V[12][i];
            vt = g.tou>m?m:g.tou;
            vd = g.dmg;
            dp[11][vt] = max(dp[11][vt], vd);
        }
        for(k=10; k>=0; k--){
            for(j=0; j<=m; j++){
                dp[k][j] = max(dp[k][j], dp[k+1][j]);
                if(dp[k+1][j]==-1)  continue;
                for(i=0; i<V[k].size(); i++){
                    g = V[k][i];
                    vt = (g.tou+j)>m?m:(g.tou+j);
                    vd = g.dmg+dp[k+1][j];
                    dp[k][vt] = max(dp[k][vt], vd);
                }
            }
        }
        printf("%d\n", dp[0][m]);
    }
    return 0;
}


评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值