题意:
你要制作n个药,初始制作一个药的时间为x,魔力值为s,有两类咒语可以加速,一类有m种咒语,每种咒语使制作一个药的时间变成a[i],花费b[i]的魔力,二类有k种咒语,每种咒语瞬间产生c[i]个药,花费d[i]的魔力,c[i]和d[i]都是不递减的,求最短时间内产生n个药的时间。
思路:
题目中明确说明了,对于第二类咒语,魔力值花的越多,瞬间产生的药也越多。所以我们可以枚举第一种咒语,二分最大的第二种咒语就行。
注意:每种咒语至多选一个,也可以不选。
二分代码:
#include <bits/stdc++.h>
using namespace std;
const int MAXN=200100;
long long a[MAXN];
long long b[MAXN];
long long c[MAXN];
long long d[MAXN];
int main(){
ios::sync_with_stdio(false);
long long num,num1,num2,x,s;
while(cin>>num>>num1>>num2>>x>>s){
for(int i=1;i<=num1;i++)
cin>>a[i];
for(int i=1;i<=num1;i++)
cin>>b[i];
for(int i=1;i<=num2;i++)
cin>>c[i];
for(int i=1;i<=num2;i++)
cin>>d[i];
a[0]=x;b[0]=c[0]=d[0]=0;
long long ans=num*x;
for(int i=0;i<=num1;i++){
long long left=s-b[i];
if(left<0) continue;
int up=upper_bound(d,d+num2+1,left)-d;
ans=min(ans,(num-c[up-1])*a[i]);
}
cout<<ans<<endl;
}
}
这个题可以用二分做的原因是,第二类咒语,魔力值花的越多,瞬间产生的药也越多。但如果把题改成第二类咒语也是乱序的,即魔力值花的多的不一定产生的药多该怎么办呢?
最直接的想法是就同时枚举两种咒语,但由于数据量的问题,肯定会TLE。O(n^2)的算法不行,有O(nlogn)的算法吗?
我们可以先枚举确定一种咒语,对于另一种咒语,我们是想知道在剩下的魔力值内,最多能产生的药的数量,或产生一瓶药最少的时间。也就是说我们要知道在一个魔力值区间内产生药最大值或时间的最小值。这时,我们就需要线段树来帮忙。
先离散化魔力值,再以魔力值递增顺序建树,维护区间的最大值或最小值。对于每种选择,做一个logn的查询就行。
这样总的算法复杂度就在nlogn级别。
线段树代码:
#include <bits/stdc++.h>
#define ls l,mid,rt<<1
#define rs mid+1,r,(rt<<1)|1
#define mi (l+r)>>1
#define root l,r,rt
using namespace std;
const long long MAXN=200100;
const long long INF=0x3f3f3f3f3f3f3f3f;
typedef struct Node{
long long x;
long long y;
Node(long long xx=0,long long yy=0){
x=xx;y=yy;
}
bool operator < (const Node &a)const{
if(a.y==y) return a.x>x;
return a.y>y;
}
}Node;
long long c[MAXN];
Node a[MAXN];
Node b[MAXN];
long long cut;
long long tree[4*MAXN];
long long st,en;
void push_up(long long l,long long r,long long rt){
tree[rt]=min(tree[rt<<1],tree[(rt<<1)|1]);
return ;
}
void build(long long l,long long r,long long rt){
if(l==r){
tree[rt]=a[cut++].x;
return ;
}
long long mid=mi;
build(ls);
build(rs);
push_up(root);
return ;
}
long long query(long long l,long long r,long long rt){
if(st<=l&&r<=en) return tree[rt];
long long mid=mi;
long long ans=INF;
if(st<=mid)
ans=min(ans,query(ls));
if(mid<en)
ans=min(ans,query(rs));
return ans;
}
int main()
{
ios::sync_with_stdio(false);
long long num,num1,num2,x,s;
while(cin>>num>>num1>>num2>>x>>s){
for(long long i=0;i<num1;i++)
cin>>a[i].x;
for(long long i=0;i<num1;i++){
cin>>a[i].y;
c[i]=a[i].y; //c数组用于离散化,以确定区间的范围
}
for(long long i=1;i<=num2;i++)
cin>>b[i].x;
for(long long i=1;i<=num2;i++)
cin>>b[i].y;
b[0].x=0;b[0].y=0;
sort(c,c+num1);sort(a,a+num1);sort(b,b+num2+1);
cut=0;
build(1,num1,1);
//long long cut;
long long ans=num*x;
for(long long i=0;i<=num2&&ans;i++){
long long left=s-b[i].y;
if(left<0) continue;
else{
ans=min(ans,max((long long)0,(num-b[i].x)*x));
if(ans==0) continue;
en=upper_bound(c,c+num1,left)-c; //二分确定区间终点
st=1; //由于是递增的区间起点一定是1
if(en<st) continue;
cut=query(1,num1,1);
ans=min(ans,(num-b[i].x)*cut);
}
}
cout<<ans<<endl;
}
}
至于为什么会想到线段树的做法呢?
那还不简单........
因为读错题了233333333333