A.Toys回到顶部
题意
贝西生日到了,他想庆祝D([1,10^5])天,第i天需要Ti([1,50])个玩具庆祝,1个玩具Tc([1,60])美元,玩具使用过了会变脏,消毒后可以再次使用
消毒方式1:1个玩具消毒N1([1,D])天,花费C1([1,60])美元
消毒方式2:1个玩具消毒N2([1,D])天,花费C2([1,60])美元
问怎么计划使得花费最少?
PS:庆祝273年,牛逼
题解
分析一下,设最小花费是f(x),x为总共买了多个玩具
显然f(x-1)>=f(x)<=f(x+1),少买或多买1个玩具会使得f变大
存在这样一种情况,买了太少的玩具使得怎样都不能满足计划,可设f(x)=inf
单峰函数存在最小值,可用三分解决,这里三分有一个细节
三分lmid=(l+r)/2,rmid=(lmid+r)/2,当lmid=rmid相等时,r-l<=2,如果移动r=rmid,假设答案恰好在r,那么完蛋
也就是说,我们需要保证r-l>2的时候才继续三分,最后[l,r]区间重新算一遍
那么问题就变成已知买了x个玩具求最小花费
显然可以贪心,我们让N1<N2,C1>C2,如果花费时间长还贵,完全可以让C2=C1
那么先用花费时间长N2,再用花费时间短的N1,还不行用x,再不行无解
该如何实现呢?
开3个双端队列,表示等待被消毒,可以用短时间消毒,可以用长时间消毒,代码1,复杂度O(Σalogn),2s接近tle
我们发现第一种方法贪心,每次放进入a[i]个待消毒,Σa复杂度很高
如何优化呢,设q[i]表示第i天剩下的,假设当前处理第i天,i-N2<=i-N1为两个时间节点
对于花费时间长的,维护一个指针top1,当top1<=i-N2可以用min(q[top1],x-a[i])个
对于花费时间短的,相当于top2=i-N1节点往前到top1可以用,发现区间[top1,i-N1]中可能某个为q[i]=0,如果不管,复杂度退化到n^2logn,
发现区间内的值只会不断减小到0,而且要么减成0,要么减成1个值,然后结束,那么就可以用并查集维护向前跳的节点直到top2<top1
代码2,复杂度O(nlognlogn),0.2s
代码
1 #include<bits/stdc++.h> 2 using namespace std; 3 4 const int N=1e5+5; 5 int a[N]; 6 int d,n1,n2,c1,c2,tc,sum; 7 deque<int>ti,ts,tl; 8 //ti 需要被消毒的数量 9 //ts 花费时间n1少的 c1元(昂贵) 10 //tl 花费事件n2长的 c2元(便宜) 11 int cal(int buy){ 12 int sumc=buy*tc; 13 ti.clear();ts.clear();tl.clear(); 14 for(int i=1;i<=d;i++){ 15 int bought=0; 16 if(buy>=a[i])bought=a[i],buy-=a[i]; 17 else bought=buy,buy=0; 18 while(!ti.empty()&&i-ti.front()>=n1){ 19 ts.push_back(ti.front()); 20 ti.pop_front(); 21 } 22 while(!ts.empty()&&i-ts.front()>=n2){ 23 tl.push_back(ts.front()); 24 ts.pop_front(); 25 } 26 while(bought<a[i]){ 27 if(!tl.empty())tl.pop_back(),bought++,sumc+=c2; 28 else if(!ts.empty())ts.pop_back(),bought++,sumc+=c1; 29 else return 1e9; 30 } 31 for(int j=1;j<=a[i];j++)ti.push_back(i); 32 } 33 return sumc; 34 } 35 int main(){ 36 while(scanf("%d%d%d%d%d%d",&d,&n1,&n2,&c1,&c2,&tc)!=EOF){ 37 if(n1>n2){ 38 swap(n1,n2); 39 swap(c1,c2); 40 } 41 //花费时间长而且还贵,那肯定不要了 42 if(c2>c1)c2=c1; 43 sum=0; 44 for(int i=1;i<=d;i++){ 45 scanf("%d",&a[i]); 46 sum+=a[i]; 47 } 48 int l=a[1],r=sum,lmid,rmid; 49 while(r-l>2){ 50 lmid=(l+r)>>1; 51 rmid=(lmid+r)>>1; 52 int lval=cal(lmid),rval=cal(rmid); 53 //printf("l=%d lmid=%d rmid=%d r=%d\n",l,lmid,rmid,r); 54 //printf("lval=%d rval=%d\n",lval,rval); 55 if(rval!=1e9&&lval<rval)r=rmid; 56 else l=lmid; 57 } 58 int ans=1e9; 59 for(int i=l;i<=r;i++)ans=min(ans,cal(i)); 60 printf("%d\n",ans); 61 } 62 return 0; 63 } 64 /* 65 4 1 3 49 23 59 66 13 67 37 68 26 69 37 70 */