[AHOI2014/JSOI2014]宅男計劃(貪心+三分)
阿新 • • 發佈:2019-01-29
今天 需要 二次 價格 食品 食物 += 分答 還要
傳送門
題意:有N種食物,分別1到N編號.第i種食物有固定的價錢Pi和保質期Si.第i種食物會在Si天後過期(特別地,如果Si=0,表示今天必須吃掉).現在有M元錢,每一次叫外賣需要額外付給外賣小哥外送費F元.外賣小哥可以一次帶來任意多份食物.求在滿足每天都能吃到至少一頓沒過期的外賣的情況下,最多可以活多少天?(突然想到白夜追兇裏有一個案件就是講的外賣小哥對那些每天宅在家中點外賣的人......額,一部挺好看的懸疑劇)
分析:本題需要我們構造貪心條件.我們現在不知道點了多少次外賣,無從下手,但我們發現,如果我們知道點了多少次外賣之後,我們就好貪心了.於是我們可以三分點了多少次外賣(至於為什麽是三分,據說要三分套二分或者二分套三分證,也可以自己代很多個值進去手算出這是一個二次函數,以後有時間證,不知道還記不記得這裏還有個坑QWQ)
ll m,f,n; struct food{ ll p,s; }a[205]; bool cmp(food x,food y){ return x.p==y.p?x.s>y.s:x.p<y.p; }//根據價格從小到大排序;價格相等,保質期從大到小排序 ll check(ll t){//t表示點了多少次外賣 ll v=m-f*t;//用總錢數m減去外賣小費f*t if(v<=0)return 0;//如果剩下的點外賣的錢小於零 ll ave=v/t;//每一次點外賣平均花費多少錢 //這裏好像也有個證明,每一次花費的錢越接近越好 //即每一次點的外賣 維持的生命的天數 越接近越好 ll sy=v-t*ave;//平攤之後剩余的錢 //下面我們就貪心一次點外賣,之後每次的方案都與這次相同 ll now=0,p,j;//now表示點一次外賣可以活多少天 for(ll i=1;i<=n;i++){ if(a[i].s>=now&&a[i].p<=ave){ p=min(a[i].s-now+1,ave/a[i].p); //你當前買的這個食品既要滿足保質期大於等於你已經買的 //食品能夠支持你續命的天數,價格還要合適,所以要取min now+=p;//累加可以活的天數 ave-=p*a[i].p;//每一份還剩下的錢 } j=i;//記錄一下枚舉到第幾份食品了 if(ave<a[i].p)break; //如果當前剩余的錢比當前枚舉到的食品的價格小,就退出 //因為我們按照價格從小到大排序,後面的都買不起了. } sy+=ave*t;//把每一份剩下的錢轉移到sy中 ll ans=0;//ans記錄剩下的錢可以續命的天數 for(int i=j;i<=n;i++){ //從第j種開始枚舉,這裏只是個小小的時間優化, //因為j之前的食品,保質期都不滿足要求了吧 if(a[i].s>=now&&a[i].p<=sy){ p=sy/a[i].p; //這裏如果像上面那樣取min,就WA了 ans+=p; sy-=p*a[i].p; } if(ans>0)break; //只要我們買了一種滿足條件的食品後,ans就會有值 //而一旦買了,剩余的錢sy就會減小,而往後枚舉,價格變大 //所以再往後枚舉,一定買不起了. } return t*now+ans; //每一份續命now天,有t份,剩余的錢還能夠續命ans天 } int main(){ m=read();f=read();n=read(); for(ll i=1;i<=n;i++){ a[i].p=read(); a[i].s=read(); } sort(a+1,a+n+1,cmp); ll l=1,r; if(f)r=m/f+1; else r=m+1; //特判一下外送小費f=0的情況(數據中會有) while(l<r){ ll midl=l+(r-l)/3; ll midr=r-(r-l)/3; if(check(midl)>=check(midr))r=midr-1; else l=midl+1; }//三分模板(自學內容) cout<<check(l)<<endl; //輸出check(l)或者check(r)都可以 //反正二分答案,三分什麽的都很玄學,"要有信仰". return 0; }
[AHOI2014/JSOI2014]宅男計劃(貪心+三分)