2156-Magic Slayer

問題概要

あなたは魔法を使ってモンスターを倒す魔法使いである.

モンスターはそれぞれHPを持っている.あなたは魔法を使ってモンスターのHPを削ることが出来る.
魔法の範囲は一体か,全ての正面に居るモンスターかの2種類があり,魔法ごとに来まっている.
モンスターはHPが0以下になると倒されたことになる.
ところで魔法は使うとMPを消費する.MPは有限なので出来るだけ消費を抑えてモンスターを倒したい.
最小のMPを求めるプログラムを書け!

解法

DP+RMQ
DPとRMQを用いて単体攻撃魔法でq以上のダメージを与える最小のMPを調べる.
そうすると全体攻撃魔法でq与えたと仮定したときの,MPの最小使用量をO(N)で求められる.
後はqを全探索する.

追記:RMQを使わなくても,線形時間の前処理で"q以上のダメージを与える最小のMP"は求められますorz

実装(C++)

RMQはSpagetthi Sourceさんのを利用しました.

#include <vector>
#include <iostream>
#include <algorithm>
#include <climits>
using namespace std;

typedef pair<int,int> P;
P all[100];
P single[100];
int allcount,singlecount;
int N,M;
int HP[100];
const int INF=99999999;
int MAX=2000000;
int *buildRMQ(int *a, int n) {
	int logn = 1;
	for (int k = 1; k < n; k *= 2) ++logn;
	int *r = new int[n * logn];
	int *b = r; copy(a, a+n, b);
	for (int k = 1; k < n; k *= 2) {
		copy(b, b+n, b+n); b += n;
		for(int i=0;i<n-k;i++) b[i] = min(b[i], b[i+k]);
	}
	return r;
}
int minimum(int x, int y, int *rmq, int n) {
	int z = y - x, k = 0, e = 1, s; // y - x >= e = 2^k なる最大 k
	s = ( (z & 0xffff0000) != 0 ) << 4; z >>= s; e <<= s; k |= s;
	s = ( (z & 0x0000ff00) != 0 ) << 3; z >>= s; e <<= s; k |= s;
	s = ( (z & 0x000000f0) != 0 ) << 2; z >>= s; e <<= s; k |= s;
	s = ( (z & 0x0000000c) != 0 ) << 1; z >>= s; e <<= s; k |= s;
	s = ( (z & 0x00000002) != 0 ) << 0; z >>= s; e <<= s; k |= s;
	return min( rmq[x+n*k], rmq[y+n*k-e+1] );
}
int sdamage[2000001];
int alldamage[2000001];
int main() {
	while(1){
		cin>>N;
		if(N==0)break;
		MAX=0;
		for(int i=0;i<N;i++){
			cin>>HP[i];
			MAX=max(MAX,HP[i]);
		}
		allcount=singlecount=0;
		cin>>M;
		bool NA=false;
		for(int i=0;i<M;i++){
			string t1,t2;
			int a,b;
			cin>>t1>>a>>t2>>b;
			MAX=max(MAX,b);
			if(b==0)continue;
			if(t2=="All"){
				all[allcount++]=(P(a,b));
			}else{
				single[singlecount++]=(P(a,b));
			}
			if(a==0&&b>0){
				NA=true;
			}
		}
		if(NA){
			cout<<"0"<<endl;
			continue;
		}
		MAX*=2;
		for(int i=0;i<=MAX;i++)sdamage[i]=alldamage[i]=INF;

		alldamage[0]=sdamage[0]=0;

		for(int j=0;j<allcount;j++){
			for(int i=0;i<=MAX;i++){
				if(i-all[j].second>=0&&alldamage[i-all[j].second]<INF){
					alldamage[i]=min(alldamage[i],alldamage[i-all[j].second]+all[j].first);
				}
			}
		}
		for(int j=0;j<singlecount;j++){
			for(int i=0;i<=MAX;i++){
				if(i-single[j].second>=0&&sdamage[i-single[j].second]<INF){
					sdamage[i]=min(sdamage[i],sdamage[i-single[j].second]+single[j].first);
				}
			}
		}
		int *rmq=buildRMQ(sdamage,MAX+1);
		int ans=INF;
		int minMP=INF;
		for(int i=MAX/2;i>=0;i--){
			int pans;
			if(alldamage[i]<INF){
				if(alldamage[i]>=minMP)continue;
				minMP=alldamage[i];
				pans=alldamage[i];
				for(int j=0;j<N;j++){
					if(HP[j]-i<=0)continue;
					pans+=minimum(HP[j]-i,MAX,rmq,MAX+1);
					if(pans>INF)break;
				}
				ans=min(pans,ans);
			}
		}
		int pans=INF;
		for(int i=MAX/2;i<=MAX;i++){
			pans=min(pans,alldamage[i]);
		}
		ans=min(ans,pans);
		cout<<ans<<endl;
		delete[] rmq;
	}
	return 0;
}