题目来源:2017“百度之星”晋级赛 1003
Time Limit: 2000/1000 MS (Java/Others)
Memory Limit: 32768/32768 K (Java/Others)
Problem description:
度度熊为了拯救可爱的公主,于是与邪恶大魔王战斗起来。
邪恶大魔王的麾下有n个怪兽,每个怪兽有a[i]的生命值,以及b[i]的防御力。
度度熊一共拥有m种攻击方式,第i种攻击方式,需要消耗k[i]的晶石,造成p[i]点伤害。
当然,如果度度熊使用第i个技能打在第j个怪兽上面的话,会使得第j个怪兽的生命值减少p[i]-b[j],当然如果伤害小于防御,那么攻击就不会奏效。
如果怪兽的生命值降为0或以下,那么怪兽就会被消灭。
当然每个技能都可以使用无限次。
请问度度熊最少携带多少晶石,就可以消灭所有的怪兽。
Input:
本题包含若干组测试数据。
第一行两个整数n,m,表示有n个怪兽,m种技能。
接下来n行,每行两个整数,a[i],b[i],分别表示怪兽的生命值和防御力。
再接下来m行,每行两个整数k[i]和p[i],分别表示技能的消耗晶石数目和技能的伤害值。
数据范围:
1<=n<=100000
1<=m<=1000
1<=a[i]<=1000
0<=b[i]<=10
0<=k[i]<=100000
0<=p[i]<=1000
Output:
对于每组测试数据,输出最小的晶石消耗数量,如果不能击败所有的怪兽,输出-1.
解析:
这是一道动态规划的题目,其思想就是比较经典的背包问题的思想。首先我们考虑单独的一个怪兽i,问打败该怪兽最少的花费是多少。
对于技能序列(k,p),进行动态规划。令数组F[i][j]代表的含义为:当1到i技能可选时,打败生命值为j的怪兽所需的最小花费。
对于打败所有怪兽的花费则是打败每个怪兽的最小花费的叠加。
这里需要统计每个怪兽被打败的最小花费,当然不能对每一个怪兽都进行一次动态规划。采取的办法是预先处理出一个cnt[i][j]数组,记录对于防御值为i, 生命值为j的怪兽所需的最小花费。我们发现防御值B_j的范围比较小,0~10,这是一个切入点。我们可以枚举B,并对应每个防御值,进行动态规划。
具体的过程分为三种情况:
1)f[i][j] = min(经此攻击之后的f’[i][j]+此攻击的花费,不采用此攻击的花费),因而限制条件是经历此攻击之后的f’[i][j]是存在的:j-p[i]+B>0,当然此攻击是有效的:p[i]>B。
if(p[i] > B && (j-p[i]+B) > 0)
f[i][j] = _min(f[i][j-p[i]+B]+k[i],f[i-1][j]);
2)如果该次攻击就已经消灭了怪兽,则直接将本次攻击的花费,与不采用此攻击的花费进行对比。
else if(p[i] > B && (j-p[i]+B) <= 0)
f[i][j] = _min(k[i],f[i-1][j]);
3)如果该攻击是无效的,则不采取该攻击,f[i][j] = f[i-1][j].
else if(p[i] <= B)
f[i][j] = f[i-1][j];
整个算法的思路就是这样。F[i][j]这个二维数组对于不同的B可以重复利用,当B更新时,对F数组进行初始化即可。每次动态规划完毕,对cnt[][]进行赋值。
Code:
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cmath>
#include <vector>
#include <cstring>
#include <queue>
#include <ctime>
#include <set>
using namespace std;
typedef long long ll;
typedef long double ld;
typedef pair<int,int> pii;
const ll INF = 10e16;
const int maxN = 100010;
const int maxM = 1010;
const int maxK = 16;
int a[maxN];
int b[maxN];
int k[maxN];
int p[maxN];
ll f[maxM][maxM];
ll cnt[12][maxM];
ll _min(ll a,ll b)
{
if(a>b)
return b;
else
return a;
}
void _clr(int m) //清空
{
for(int i=0;i<=m;i++)
for(int j=0;j<maxM;j++)
{
if(i==0 && j==0)
f[i][j] = 0;
else
f[i][j] = INF;
}
}
int main()
{
int n,m;
while(scanf("%d%d",&n,&m) !=EOF)
{
for(int i=1;i<=n;i++)
{
scanf("%d%d",&a[i],&b[i]);
}
int maxp = 0; //找到最大攻击力
for(int i=1;i<=m;i++)
{
scanf("%d%d",&k[i],&p[i]);
maxp = max(maxp,p[i]);
}
for(int B=0;B<=10;B++)
{
_clr(m);
for(int i=1;i<=m;i++)
{
for(int j=1;j<maxM;j++)
{
if(p[i] > B && (j-p[i]+B) > 0)
f[i][j] = _min(f[i][j-p[i]+B]+k[i],f[i-1][j]);
else if(p[i] > B && (j-p[i]+B) <= 0)
f[i][j] = _min(k[i],f[i-1][j]);
else if(p[i] <= B)
f[i][j] = f[i-1][j];
}
}
for(int j=1;j<maxM;j++)
{
cnt[B][j] = f[m][j];
}
}
ll ans = 0;
int flag = 0;
for(int i=1;i<=n;i++)
{
int x = a[i];
int y = b[i];
if( y>= maxp) //有一个怪兽 的防御值 大于等于最大攻击值
{
flag = 1;
break;
}
ans += cnt[y][x];
}
if(!flag)
printf("%lld\n",ans);
else
puts("-1");
}
return 0;
}