题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=2809
题目大意:给出战神吕布的初始攻击力ATI、防御力DEF、生命值HP、每升一级增加的攻击力In_ATI,增加的防御力In_DEF和增加的生命值In_HP。然后给出n个敌人的攻击力、防御力、生命值和杀死该单位能获得的经验值EXP。 吕布的初始经验值EXP是0,初始等级level是1,每当EXP>=level*100时就会升级。
在吕布LvBu和敌人A之间的战斗有3条规则
1.吕布攻击A,A失去 Max(1,LvBu's ATI- A's DEF) HP;
2.A攻击吕布,吕布失去Max(1,A'ATI- LvBu'DEF)HP;
3.战斗持续到任意一方死亡
如果吕布能够将全部敌人杀死,并且自己不死,那么输出能够余下的最大HP,否则输出"Poor LvBu,his period was gone."
分析:
由于涉及到最优结果 以及 单挑顺序对结果带来的影响,使用状态压缩dp来解这道题。
想讲一下思路,比如现在吕布跟三个人战斗,用1表示已经战胜这个人, 0表示没有。那个所有的情况(在不考虑顺序的情况下)就有2的3次方,8种可能,包括000。用对应的十进制表示。如图
可见,3的状态可以由1,2来求,5可以用1,4来求,6可以用2, 4来求,7可以用3, 5, 6状态来求,这就有了一个状态转移的方程在里面了。
有些同学就想问那顺序怎么办呢。
我们要计算3的最优状态的时候,会由1,跟2,来推出
1状态表示打败第一个人
2状态只表示第二个人
那么3状态由1状态来计算的时候,就是先打第一个人再打第二个人
而3状态由2状态来计算的时候,就是先打第二个人再打第一个人, 计算后的结果与当前的3状态的结果比较,把最优的覆盖上去就可以了。
这个过程是不是有了一个顺序在里面了呢。
而在代码里面,我们是由底向上计算额。比如历遍到第1个状态点的。我们可以计算上面的第3个点,跟第5个状态点,寻找的过程由位运算来实现。
代码如下:
1 # include<stdio.h> 2 # include<string.h> 3 int max(int a,int b){ 4 return a>b ?a :b; 5 } 6 struct Lvbu{ 7 int ATI,DEF,HP,EXP,level; 8 }lv,dp[1<<20]; //dp[i]表示在在集合i下,吕布能达到的最好状态 9 10 struct Enemy{ 11 char name[25]; 12 int ATI,DEF,HP,EXP; 13 }en[20]; 14 15 int In_ATI,In_DEF,In_HP,n; 16 17 int main(){ 18 int i,j; 19 while(scanf("%d%d%d%d%d%d",&lv.ATI,&lv.DEF,&lv.HP,&In_ATI,&In_DEF,&In_HP) != EOF){ 20 lv.EXP = 0; //吕布初始经验为0,初始等级为1 21 lv.level = 1; 22 scanf("%d",&n); 23 for(i=0;i<n;i++) 24 scanf("%s%d%d%d%d",en[i].name,&en[i].ATI,&en[i].DEF,&en[i].HP,&en[i].EXP); 25 int s,recent; 26 int endstate = 1<<n; 27 dp[0] = lv; //dp[0]表示集合0,即没有攻击任何一个敌人时,吕布的状态,初始化 28 bool flag; 29 30 for(s=1; s<endstate; s++){ 31 flag = false; //判断是否可以打败敌人 32 for(i=0; i<n; i++){ 33 recent = 1<<i; 34 if(s & recent){ //如果在该集合里边,吕布打败了第i个敌人 35 Lvbu tmp=dp[s^recent]; //取出吕布没有打败第i个人,但是打败了状态s里边的其他所有人 36 if(tmp.level < 1) continue; //已经死亡 37 int dec = tmp.ATI - en[i].DEF; //吕布的攻击力 38 if(dec <= 0) dec = 1; //特殊情况 39 40 int times = en[i].HP / dec; //吕布杀死敌人的时间 41 if(times * dec != en[i].HP) times++; 42 43 dec = en[i].ATI - tmp.DEF; //敌人的攻击力 44 if(dec <= 0) dec = 1; 45 tmp.HP -= dec*(times - 1); //吕布受到的伤害 46 47 if(tmp.HP <= 0) continue; //如果死亡,跳出 48 49 tmp.EXP += en[i].EXP; //获得该角色经验 50 if(tmp.EXP >= tmp.level* 100){ //如果经验足够升级 51 int add = tmp.EXP/100 - tmp.level + 1; //可升多少级 52 tmp.level += add; //各项技能强化 53 tmp.ATI += add*In_ATI; 54 tmp.DEF += add*In_DEF; 55 tmp.HP += add*In_HP; 56 } 57 if(!flag || (tmp.HP>dp[s].HP) ||tmp.level>dp[s].level){ //选取最优的方案,并且置flag为真 58 dp[s] = tmp; 59 flag = true; 60 } 61 } 62 } 63 if(!flag) dp[s].level = -1; //如果不能打败该集合里的敌人,那么令等级为-1 64 } 65 66 lv = dp[endstate -1]; //将所有敌人打倒的状态 (1<<n)-1 67 if(lv.level < 1) 68 printf("Poor LvBu,his period was gone. "); 69 else 70 printf("%d ",lv.HP); 71 } 72 return 0; 73 }