[BZOJ2964]Boss单挑战
Online Judge:Bzoj-2964
Label:Dp,神题,模拟,题面恶心
Description
某RPG游戏中,最后一战是主角单挑Boss,将其简化后如下:
主角的气血值上限为HP,魔法值上限为MP,愤怒值上限为SP;Boss仅有气血值,其上限为M。
现在共有N回合,每回合都是主角先行动,主角可做如下选择之一:
- 普通攻击:减少对方X的气血值,并增加自身DSP的愤怒值。(不超过上限)
- 法术攻击:共有N1种法术,第i种消耗Bi的魔法值,减少对方Yi的气血值。(使用时要保证MP不小于Bi)
- 特技攻击:共有N2种特技,第i种消耗Ci的愤怒值,减少对方Zi的气血值。(使用时要保证SP不小于Ci)
- 使用HP药水:增加自身DHP的气血值。(不超过上限)
- 使用MP药水:增加自身DMP的魔法值。(不超过上限)
之后Boss会攻击主角,在第i回合减少主角Ai的气血值。
刚开始时气血值,魔法值,愤怒值都是满的。当气血值小于等于0时死亡。
如果主角能在这N个回合内杀死Boss,那么先输出“Yes”,之后在同一行输出最早能在第几回合杀死Boss。(用一个空格隔开)
如果主角一定会被Boss杀死,那么输出“No”。
其它情况,输出“Tie”。
Input
输入的第一行包含一个整数T,为测试数据组数。
接下来T部分,每部分按如下规则输入:
第一行九个整数(N, M, HP, MP, SP, DHP, DMP, DSP, X)。
第二行N个整数(Ai)。
第三行第一个整数N1,接下来包含N1对整数(Bi, Yi)。
第四行第一个整数N2,接下来包含N2对整数(Ci, Zi)。
Output
输出共包含T行,每行依次对应输出一个答案。
Sample Input
2
5 100 100 100 100 50 50 50 20
50 50 30 30 30
1 100 40
1 100 40
5 100 100 100 100 50 50 50 10
50 50 30 30 30
1 100 40
1 100 40
Sample Output
Yes 4
Tie
样例说明: 对于第一个样例,主角的策略是:第一回合法术攻击,第二回合使用HP药水,第三回合特技攻击,第四回合普通攻击。
HINT
对于100%的数据:(1 ≤ N ≤ 1000) ,(1 ≤ M ≤ 1000000) , (1 ≤ HP,MP,SP ≤ 1000) (N1,N2 ≤ 10) , (DHP,Ai ≤ HP) , (DMP,Bi ≤ MP) , (DSP,Ci ≤ SP) , (X,Yi,Zi ≤ 10000) , (1 ≤ T ≤ 10)。
题解:
离线赛时,剩的时间不多了但是感觉60%数据的Dp还是挺好打的??于是快速敲完,但是最后没调出来..样例都没过,然后0分。但是赛后又打了一种暴力的Dp,结果同样样例没过但是有50分???
暴力Dp有两个瓶颈,一个是时间:要枚举第几轮i,当前血量hp,当前魔法值mp,当前愤怒值sp,对于100%数据肯定承受不了;还有内存:存状态时用到上面那四维,对于100%数据范围会直接爆炸。
首先对于这种变量多的题目一定要保持思路清晰,所有约束条件一起考虑Dp不仅容易出bug,还会有很多细节。所以考虑能不能将这几种变量分开dp处理。
接下来我们将除嗑血瓶(对应题面操作4)外的其他操作统称为A操作,发现,假如自己血量无限那每轮都可以进行A操作,但是有了血量的限制,我们有时不得不嗑血瓶,所以这两者形成制约。但是,A操作之间却没有很大的制约关系(除了对round数量的考虑),所以先处理完A操作,然后再去考虑嗑血瓶。
A操作主要是针对魔法值、怒气值进行的,我们将两者分开处理:
对于魔法值:
操作 | 魔法值变化 | 对boss的输出 |
---|---|---|
嗑魔法药(对应题面操作5) | (+Dmp) | (0) |
使用法术i(对应题面操作2) | (-b[i]) | (+y[i]) |
定义状态(dp1[i][j])表示当前进行了i次与魔法值相关的操作且当前魔法值为j能对boss产生的最大伤害。用(maxn1[i])表示进行了i次与魔法值相关的操作最多能对boss产生多少伤害,这个预处理为后面做准备。
code:
//魔法 Mp
For(i,0,n){
For(j,0,Mp)Up(maxn1[i],dp1[i][j]);
if(i!=n)For(j,0,Mp){
//operator1:嗑魔法药
Up(dp1[i+1][min(Mp,j+Dmp)],dp1[i][j]);
//operator2:法攻
For(k,1,cnt_mp)if(b[k]<=j){
Up(dp1[i+1][j-b[k]],dp1[i][j]+y[k]);
}
}
}
对于愤怒值:
操作 | 愤怒值变化 | 对boss的输出 |
---|---|---|
普攻(对应题面操作1) | (+Dsp) | (X) |
使用特技i(对应题面操作3) | (-c[i]) | (+z[i]) |
定义状态(dp2[i][j])表示当前进行了i次与愤怒值相关的操作且当前愤怒值为j能对boss产生的最大伤害。用(maxn2[i])表示进行了i次与愤怒值相关的操作最多能对boss产生多少伤害,这个预处理t同样为后面做准备。
code:
//怒气 Sp
For(i,0,n){
For(j,0,Sp)Up(maxn2[i],dp2[i][j]);
if(i!=n)For(j,0,Sp){
//operator1:普攻
Up(dp2[i+1][min(Sp,j+Dsp)],dp2[i][j]+X);
//operator2:特技???2333
For(k,1,cnt_sp)if(c[k]<=j){
Up(dp2[i+1][j-c[k]],dp2[i][j]+z[k]);
}
}
}
接下来先忽略血量,我们就可以求出至少要用几次A操作就能干掉boss——下面代码中的times。
int times=INF;//最少进行几次除嗑血药的操作 能够干掉boss
For(i,0,n)For(j,0,n){
if(maxn1[i]+maxn2[j]>=M)times=min(times,i+j);
}
然后结合自身血量一起考虑,定义状态(f[i][j])表示到第i轮保证自身不死,最多能进行几次A操作。那么当某轮的(f[i][j])大于等于times时就代表这轮可以干掉boss。
f[1][Hp]=1;
For(i,1,n){
For(j,1,Hp)if(f[i][j]>=times){
printf("Yes %d
",i);
return;
}
For(j,1,Hp){
int if_Dhp=min(Hp,j+Dhp)-a[i];
if(if_Dhp>0)Up(f[i+1][if_Dhp],f[i][j]);
if(j-a[i]>0)Up(f[i+1][j-a[i]],f[i][j]+1);
}
}
上面已经处理完了最少第几轮能干掉boss的情况,还要考虑在n轮内一定会被boss的干掉的情况——"No",和在n轮内谁都不能干掉谁的情况——"Tie"。
//能存活到n+1轮
For(i,1,Hp)if(f[n+1][i]>=0){
puts("Tie");return;
}
puts("No");
本题到此结束,注意前面的初始化。
当题目信息过多难以维护时,考虑根据制约强度分开处理。
完整代码如下☞
//神题,三个Dp合成,变量多,分析一下还是挺好搞的
#include<bits/stdc++.h>
using namespace std;
#define For(a,b,c) for(register int a=b;a<=c;++a)
inline int read(){
int x=0;char c=getchar();
while(c<'0'||c>'9')c=getchar();
while(c>='0'&&c<='9')x=(x<<3)+(x<<1)+(c^48),c=getchar();
return x;
}
const int N=1010,INF=123456789;
int b[12],y[12],c[12],z[12];
int maxn1[N],maxn2[N],dp1[N][1010],dp2[N][1010];
int f[N][1010],a[N];
inline void Up(int &a,int b){if(a<b)a=b;}
void init(){
memset(maxn1,0,sizeof(maxn1)),memset(maxn2,0,sizeof(maxn2));
memset(dp1,0,sizeof(dp1)),memset(dp2,0,sizeof(dp2));
For(i,0,1005)For(j,0,1005)f[i][j]=-INF;
}
void solve(){
int n=read(),M=read(),Hp=read(),Mp=read(),Sp=read();
int Dhp=read(),Dmp=read(),Dsp=read(),X=read();
For(i,1,n)a[i]=read();
int cnt_mp=read();For(i,1,cnt_mp)b[i]=read(),y[i]=read();
int cnt_sp=read();For(i,1,cnt_sp)c[i]=read(),z[i]=read();
For(i,0,n){
For(j,0,Mp)Up(maxn1[i],dp1[i][j]);
if(i!=n)For(j,0,Mp){
Up(dp1[i+1][min(Mp,j+Dmp)],dp1[i][j]);
For(k,1,cnt_mp)if(b[k]<=j){
Up(dp1[i+1][j-b[k]],dp1[i][j]+y[k]);
}
}
}
For(i,0,n){
For(j,0,Sp)Up(maxn2[i],dp2[i][j]);
if(i!=n)For(j,0,Sp){
Up(dp2[i+1][min(Sp,j+Dsp)],dp2[i][j]+X);
For(k,1,cnt_sp)if(c[k]<=j){
Up(dp2[i+1][j-c[k]],dp2[i][j]+z[k]);
}
}
}
int times=INF;
For(i,0,n)For(j,0,n){
if(maxn1[i]+maxn2[j]>=M)times=min(times,i+j);
}
f[1][Hp]=1;
For(i,1,n){
For(j,1,Hp)if(f[i][j]>=times){
printf("Yes %d
",i);
return;
}
For(j,1,Hp){
int if_Dhp=min(Hp,j+Dhp)-a[i];
if(if_Dhp>0)Up(f[i+1][if_Dhp],f[i][j]);
if(j-a[i]>0)Up(f[i+1][j-a[i]],f[i][j]+1);
}
}
For(i,1,Hp)if(f[n+1][i]>=0){
puts("Tie");return;
}
puts("No");
}
int main(){
int T=read();
For(cas,1,T)init(),solve();
return 0;
}