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。
Source
中国国家队清华集训 2012-2013 第三天
一眼背包无疑,这是我做过的最长最难的背包dp了。这道题需要考虑三个东西,魔法、怒气和生命值。
如果直接将三个东西放在一起转移,想必让我推一天也推不出来方程,毕竟状态实在太多了。
那么我们可以换个思路,将三个东西分开处理。
先赋代码,再讲解:
#include<queue>
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
const int INF=0x3f3f3f3f;
int a[1001],dpmp[1001],dpsp[1001],dphp[1001][1001];
int gmp[1001][1001],gsp[1001][1001];
int b[11],y[11],c[11],z[11];
inline int read()
{
int x=0,f=1;char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
return x*f;
}
void solve()
{
memset(gmp,0,sizeof(gmp));
memset(gsp,0,sizeof(gsp));
memset(dpmp,0,sizeof(dpmp));
memset(dpsp,0,sizeof(dpsp));
memset(dphp,-INF,sizeof(dphp));
int n,m,hp,mp,sp,dhp,dmp,dsp,x;
n=read();m=read();hp=read();mp=read();sp=read();
dhp=read();dmp=read();dsp=read();x=read();
for(int i=1;i<=n;i++)
a[i]=read();
int n1=read();
for(int i=1;i<=n1;i++)
{
b[i]=read();y[i]=read();
}
int n2=read();
for(int i=1;i<=n2;i++)
{
c[i]=read();z[i]=read();
}
for(int i=0;i<=n;i++)
{
for(int j=0;j<=mp;j++)
dpmp[i]=max(dpmp[i],gmp[i][j]);
if(i<n)
for(int j=0;j<=mp;j++)
{
gmp[i+1][min(mp,j+dmp)]=max(gmp[i+1][min(mp,j+dmp)],gmp[i][j]);
for(int k=1;k<=n1;k++)
if(j-b[k]>=0)
gmp[i+1][j-b[k]]=max(gmp[i+1][j-b[k]],gmp[i][j]+y[k]/**/);
}
}
for(int i=0;i<=n;i++)
{
for(int j=0;j<=sp;j++)
dpsp[i]=max(dpsp[i],gsp[i][j]);
if(i<n)
for(int j=0;j<=sp;j++)
{
gsp[i+1][min(sp,j+dsp)]=max(gsp[i+1][min(sp,j+dsp)],gsp[i][j]+x);
for(int k=1;k<=n2;k++)
if(j-c[k]>=0)
gsp[i+1][j-c[k]]=max(gsp[i+1][j-c[k]],gsp[i][j]+z[k]/**/);
}
}
int minx=INF;
for(int i=0;i<=n;i++)
for(int j=0;j<=n;j++)
if(dpmp[i]+dpsp[j]>=m)
minx=min(minx,j+i);
dphp[1][hp]=1;
for(int i=1;i<=n;i++)
{
for(int j=1;j<=hp;j++)
if(dphp[i][j]>=minx)
{
printf("Yes %d\n",i);
return ;
}
for(int j=1;j<=hp;j++)
{
if(min(j+dhp,hp)>a[i])
dphp[i+1][min(j+dhp,hp)-a[i]]=max(dphp[i+1][min(j+dhp,hp)-a[i]],dphp[i][j]);
if(j>a[i])
dphp[i+1][j-a[i]]=max(dphp[i+1][j-a[i]],dphp[i][j]+1);
}
}
for(int i=1;i<=hp;i++)
if(dphp[n+1][i]>=0)
{
printf("Tie\n");
return ;
}
printf("No\n");
return ;
}
int main()
{
int T=read();
while(T--)
solve();
return 0;
}
我直接说 solve 里面的东西了。
gmp[i][j] 表示不管 hp 和 sp,进行到第 i 个回合时还剩 j mp 时最大的魔法攻击值,同理 gsp[i][j] 表示不管 hp 和 mp,进行到第 i 个回合时还剩 j sp 时最大的怒气攻击值。
dpmp 和 dpsp 就直接表示进行到第 i 回合时的最大攻击值。
for(int i=0;i<=n;i++)
{
for(int j=0;j<=mp;j++)
dpmp[i]=max(dpmp[i],gmp[i][j]);//将g数组赋回dp数组
if(i<n)
for(int j=0;j<=mp;j++)
{
gmp[i+1][min(mp,j+dmp)]=max(gmp[i+1][min(mp,j+dmp)],gmp[i][j]);//此处嗑蓝瓶
for(int k=1;k<=n1;k++)
if(j-b[k]>=0)
gmp[i+1][j-b[k]]=max(gmp[i+1][j-b[k]],gmp[i][j]+y[k]/**/);//进行攻击
}
}
sp 的方程一样,不再赘述。
然后的代码是这样:
int minx=INF;
for(int i=0;i<=n;i++)
for(int j=0;j<=n;j++)
if(dpmp[i]+dpsp[j]>=m)
minx=min(minx,j+i);
dphp[1][hp]=1;
这里的意思是算出将Boss打死需要的最小回合数。算出 minx 之后只需算 hp,在 hp>0 的情况下的最小大于minx的回合数就行。
dphp[i][j] 表示进行到第 i 回合,还剩 j hp 时可以攻击的最大次数。
算 hp:
for(int i=1;i<=n;i++)
{
for(int j=1;j<=hp;j++)
if(dphp[i][j]>=minx)//如果攻击次数大于minx,就Yes
{
printf("Yes %d\n",i);
return ;
}
for(int j=1;j<=hp;j++)
{
if(min(j+dhp,hp)>a[i])
dphp[i+1][min(j+dhp,hp)-a[i]]=max(dphp[i+1][min(j+dhp,hp)-a[i]],dphp[i][j]);//嗑红瓶
if(j>a[i])
dphp[i+1][j-a[i]]=max(dphp[i+1][j-a[i]],dphp[i][j]+1/*可以攻击就+1*/);
}
}
最后如果没有输出Yes,那么先这样:
for(int i=1;i<=hp;i++)
if(dphp[n+1][i]>=0)
{
printf("Tie\n");
return ;
}
也就是说,如果双方都打不死对方,就输出Tie。
无法输出Tie,就输出No。
printf("No\n");
return ;
多好的一题啊。