T1 大天使之剑
【问题描述】
小A在游戏⾥打怪。有⼀次,他⼀下⼦遇到了n个怪物。
每个怪物有一个生命值,第i个怪物的生命值是h_i。而小A除了生命值之外,还有一个属性是魔法值m。
小A和怪物们依次行动。每一回合,小A先行动,然后怪物们同时行动。
小A每次可以选择以下行动之一:
•普通攻击:令某个怪物的生命值减少1。
•重击:消耗1魔法值,令某个怪物的生命值减少2。
•群体攻击:消耗1魔法值,令全体怪物的生命值减少1。
而每个存活的怪物(生命值严格大于0)每次会令小A的生命值减少1。
假设小A有足够的生命值来维持存活,小A想知道自己至少需要被消耗多少生命值。
【输入文件】
输入文件为zhijian.in。
第一行为两个数n和m。
第二行为n个整数,第i个数为h_i。
【输出文件】
输出文件为zhijian.out。
输出一个整数,即小A至少被消耗的生命值。
【输入样例1】
2 1
2 1
【输出样例1】
1
【输入样例2】
3 4
2 4 4
【输出样例2】
6
【数据规模和约定】
对于20%的数据,m≤0;
对于30%的数据,m≤1;
对于50%的数据,m≤18;
存在30%的数据,n≤50,h_i≤50;
m=0与m=18各存在1个测试点,n≤1000,h_i≤1000;
对于100%的数据,1≤n≤100,000,0≤m≤100,0<h_i≤100,000。
一眼贪心无疑,问题是该怎么贪。
当还有魔法值时肯定是先群攻或重击。举些例子后可以很轻松地发现:当剩下的怪物大于2个时或当前怪物生命值为1肯定是群攻,否则为重击;魔法值为0时直接模拟即可(也可以不模拟,用个类似前缀和的形式直接算)。
代码(讨论了很多,可能有点丑):
#include<queue>
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#ifdef WIN32
#define AUTO "%I64d"
#else
#define AUTO "%lld"
#endif
using namespace std;
long long h[100005],num[100005],judge[100005],cnt[100005],bb[100005];
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;
}
int main()
{
freopen("zhijian.in","r",stdin);
freopen("zhijian.out","w",stdout);
int n=read(),m=read(),tot=0;
long long ans=0,opp=0;
for(int i=1;i<=n;i++)
{
scanf(AUTO,&h[i]);
num[h[i]]++;
if(!judge[h[i]])
{
judge[h[i]]=1;
cnt[++tot]=h[i];
}
}
sort(cnt+1,cnt+tot+1);
int left=n;
while(m>0)
{
if(left>2)
{
m--;
ans+=left-num[1];
left-=num[1];
if(num[1]>0)
{
num[1]=0;
cnt[1]=cnt[2]-1;
for(int i=2;i<=tot;i++)
{
if(num[cnt[i]]>0)
{
num[cnt[i]-1]=num[cnt[i]];
num[cnt[i]]=0;
if(i!=tot)
cnt[i]=cnt[i+1]-1;
}
}
cnt[tot]=0;
tot--;
}
else
{
for(int i=1;i<=tot;i++)
{
if(num[cnt[i]]>0)
{
num[cnt[i]-1]+=num[cnt[i]];
num[cnt[i]]=0;
cnt[i]-=1;;
}
}
}
}
else
{
if(left==2)
{