[BZOJ2964]Boss单挑战

[BZOJ2964]Boss单挑战

Online Judge:Bzoj-2964

Label:Dp,神题,模拟,题面恶心

Description

题目链接

某RPG游戏中,最后一战是主角单挑Boss,将其简化后如下:

  主角的气血值上限为HP,魔法值上限为MP,愤怒值上限为SP;Boss仅有气血值,其上限为M

  现在共有N回合,每回合都是主角先行动,主角可做如下选择之一:

  1. 普通攻击:减少对方X的气血值,并增加自身DSP的愤怒值。(不超过上限)
  2. 法术攻击:共有N1种法术,第i种消耗Bi的魔法值,减少对方Yi的气血值。(使用时要保证MP不小于Bi)
  3. 特技攻击:共有N2种特技,第i种消耗Ci的愤怒值,减少对方Zi的气血值。(使用时要保证SP不小于Ci)
  4. 使用HP药水:增加自身DHP的气血值。(不超过上限)
  5. 使用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\n",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\n",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;
}
posted @ 2019-07-31 16:09  real_lyb  阅读(347)  评论(1编辑  收藏  举报