题目链接:http://codeforces.com/contest/611/problem/E
题意:有三个抢手能力值分别为a,b,c。另外有n个小怪抵抗力为t(i)。当一个抢手能能力值大于等于小怪的抵抗力时抢手可以在一回合内杀死小怪。多个抢手同时联手当其能力值和大于等于某小怪抵抗力时可以再一回合内杀死小怪。问三个抢手最少多少回合能将小怪全部杀死。
解法:先贪心,后枚举。
1.贪心部分:把抢手能力值排序使a<=b<=c,把怪物按抵抗力从低到高排序,看是否a+b+c能消灭所有怪物,不能就输出“-1”。如果能,看是否有必要使用a+b+c。然后看是否有必要使用b+c,每使用一次b+c时,可以使用一次a(使用时发挥a的最大效能)。然后看是否有必要使用a+c,没使用一次a+c,可以使用一次b(发挥最大效能)。贪心部分的回合数为ans。
2.枚举部分:下面就面临着一个问题,如何使用a+b和c,假设a+b能消灭剩余的怪中的x个,c能消灭y个,那么如果只用a+b和c最快可以再max((x+y)/2,y-x,x-y)回合数内消灭全部怪物。那么我们就枚举只单独使用a、b、c的回合数,可以得到使用 a 或 b 或 c 或 a+b 时消灭怪物的最少次数,记为add。
最终的答案就是ans+add。(枚举部分需要体会理解,表示智商低,想了一天才想明白)。
3.实现时的技巧:将小怪抵抗力全部插入一个multiset中,需要查询小于等于某个值的最大抵抗力,而STL中的lower_bound(val)返回的是左开右闭范围中大于等于val的元素的位置,如果范围内全部值都小于val,则返回尾部空指针。这就需要我们在处理抵抗力时将所有抵抗力减一再插入multiset中,这样的好处是查询时只需要查询小于某个值的最大抵抗力,就可以直接使用lower_bound(val)而不需要判断得到的值是否等于val,可以大大减少代码量。
下付两种实现方法,大家可以体会:
1.抵抗力减一再插入:
#include <iostream>
#include <cstdio>
#include <cstring>
#include <string>
#include <algorithm>
#include <cmath>
#include <vector>
#include <set>
#include <map>
#include <stack>
#include <queue>
#include <iterator>
using namespace std;
multiset<int> kill;
int w[3];
multiset<int>::iterator it,it2;
int ans;
void greedy(int dl,int pre){
while(kill.size()!=0){
it=kill.end();
it--;
if(*it<dl) break;
kill.erase(it);
ans++;
it=kill.lower_bound(pre);
if(it!=kill.begin()){
it--;
kill.erase(it);
}
}
}
int main (){
int n;
int a,b,c;
while(scanf("%d",&n)!=EOF){
scanf("%d%d%d",&w[0],&w[1],&w[2]);
sort(w,w+3);
kill.clear();
int x;
for(int i=1;i<=n;i++){
scanf("%d",&x);
x--;
kill.insert(x);
}
it=kill.end();
it--;
if(*it>=w[0]+w[1]+w[2]){
printf("-1\n");
continue;
}
ans=0;
greedy(w[1]+w[2],0);
greedy(w[0]+w[2],w[0]);
greedy(max(w[0]+w[1],w[2]),w[1]);
int one=0,two=0;
it=kill.begin();
while(it!=kill.end()){
if(*it<w[0]+w[1]) two++;
if(*it<w[2]) one++;
it++;
}
int add=n*10;
for(int i=0;i<=n;i++){
if(2*min(one,two)>=kill.size()){
add=min(add,i+((int)kill.size()+1)/2);
}
else{
add=min(add,i+(int)kill.size()-min(one,two));
}
for(int j=0;j<=2;j++){
it2=kill.lower_bound(w[j]);
if(it2!=kill.begin()){
it2--;
if(*it2<w[0]+w[1]) two--;
if(*it2<w[2]) one--;
kill.erase(it2);
}
}
}
printf("%d\n",ans+add);
}
return 0;
}
2.抵抗力直接插入:
#include <iostream>
#include <cstdio>
#include <cstring>
#include <string>
#include <algorithm>
#include <cmath>
#include <vector>
#include <set>
#include <map>
#include <stack>
#include <queue>
#include <iterator>
using namespace std;
multiset<int> kill;
int w[3];
multiset<int>::iterator it,it2;
int ans;
void greedy(int dl,int pre){
while(kill.size()!=0){
it=kill.end();
it--;
if(*it<=dl) break;
kill.erase(it);
ans++;
if(!kill.empty()){
it=kill.lower_bound(pre);
if(kill.size()==1){
if(it==kill.end()){
it--;
kill.erase(it);
}
else
if(*it<=pre)
kill.erase(it);
}
else{
if(it!=kill.end()&&*it<=pre)
kill.erase(it);
else{
if(it!=kill.begin()){
it--;
kill.erase(it);
}
}
}
}
}
}
int main (){
int n;
int a,b,c;
while(scanf("%d",&n)!=EOF){
scanf("%d%d%d",&w[0],&w[1],&w[2]);
sort(w,w+3);
kill.clear();
int x;
for(int i=1;i<=n;i++){
scanf("%d",&x);
kill.insert(x);
}
it=kill.end();
it--;
if(*it>w[0]+w[1]+w[2]){
printf("-1\n");
continue;
}
ans=0;
greedy(w[1]+w[2],0);
greedy(w[0]+w[2],w[0]);
greedy(max(w[0]+w[1],w[2]),w[1]);
int one=0,two=0;
it=kill.begin();
while(it!=kill.end()){
if(*it<=w[0]+w[1]) two++;
if(*it<=w[2]) one++;
it++;
}
int add=n*10;
for(int i=0;i<=n;i++){
if(2*min(one,two)>=kill.size()){
add=min(add,i+((int)kill.size()+1)/2);
}
else{
add=min(add,i+(int)kill.size()-min(one,two));
}
for(int j=0;j<=2;j++){
if(!kill.empty()){
it2=kill.lower_bound(w[j]);
if(kill.size()==1){
if(it2==kill.end()){
it2--;
if(*it2<=w[0]+w[1]) two--;
if(*it2<=w[2]) one--;
kill.erase(it2);
}
else{
if(*it2<=w[j]){
if(*it2<=w[0]+w[1]) two--;
if(*it2<=w[2]) one--;
kill.erase(it2);
}
}
}
else{
if(it2!=kill.end()&&*it2<=w[j]){
if(*it2<=w[0]+w[1]) two--;
if(*it2<=w[2]) one--;
kill.erase(it2);
}
else{
if(it2!=kill.begin()){
it2--;
if(*it2<=w[0]+w[1]) two--;
if(*it2<=w[2]) one--;
kill.erase(it2);
}
}
}
}
}
}
printf("%d\n",ans+add);
}
return 0;
}