首先,先把機器依照購買時間排序,那麼就可以設計一個$DP$,$DP[i]$代表到達第i台機器的購買時間之前,賣掉所有機器可獲得的最大收入
為了方便說明,定義$M_{i}$為排序後的第i台機器
$M_{i}.day$代表這台機器的購買時間
$M_{i}.price$代表這台機器購入的價格
$M_{i}.resell$代表這台機器賣出的價格
$M_{i}.profit$代表這台機器每天能產生的利潤
可以發現,當購入第$i$台機器並開始無止盡的運作,你的錢隨著時間變化會是一條斜率$>0$的斜直線,而這條斜直線會和$x=Mi.day$交叉於$y=DP[i]+(Mi.resell−Mi.price)$,因此截距是$DP[i]+(Mi.resell−Mi.price)−Mi.profit∗Mi.day$ (看看DP的定義,想一想,為甚麼XD)
首先,如果我們知道$DP[a]、DP[a+1]、...、DP[b]$,我們就可以拿第$a\sim b$台機器賺最多的錢去分別更新第$b+1\sim N$台機器的最大值了
$_{註1}$考慮使用單調凸包優化$DP$,把第$a\sim b$台機器依照賺錢速度 (斜率) 排序,這樣一來,和這個做法不一樣的是,我們多了斜率的單調性,不用再做麻煩的二分搜和平衡樹動態增刪值了!
透過$deque$ (之後會發現只用到$stack$的功能,因為可以先做好凸包,才開始更新) 實作凸包優化$DP$,我們可以實現複雜度$O(n+m)$(假設拿$n$台機器去更新之後的其中$m$台機器)的更新
問題是,要用哪些機器來更新哪些機器呢(才不會讓複雜度退化)?
我們使用操作分治的思想
首先,如果我們知道了$DP[1]$,就可以拿去更新$DP[2]$ ($DP[2]$確定了)
我們知道了$DP[1]$和$DP[2]$,就去更新$DP[3]$和$DP[4]$ ($DP[3]$確定了)
這下$DP[3]$也確定了,因此拿去更新$DP[4]$ ($DP[4]$確定了)
這下我們知道了$DP[1]$、$DP[2]$、$DP[3]$和$DP[4]$,就去更新$DP[5]$、$DP[6]$、$DP[7]$和$DP[8]$ ($DP[5]$確定了)
......
有點倍增的感覺對不對?
可以發現,以上的做法可以看做是在一棵線段樹上依照遍歷順序,拿左子樹包含的區間 (可以保證這時左子樹的所有$DP$都已經確定了,想一想,為甚麼XD) 去一口氣更新右子樹包含的區間
知道了用甚麼順序更新,使用$_{註1}$講的方法,處理線段樹 (我覺得這裡還是要強調一下,這棵「線段樹」只是提供想像上的框架,不用真的建出來) 的每一層的時間複雜度是$O(N)$ (想一想,為甚麼XD)
總時間複雜度:$O(N\log N)$
code:
#include<cstdio> #include<cassert> #include<vector> #include<algorithm> using namespace std; typedef long long LL; void getmax(LL &a,const LL &b){if(b>a)a=b;} struct Machine { LL day,price,resell,profit; Machine(){} Machine(const LL &_day,const LL &_price,const LL &_resell,const LL &_profit):day(_day),price(_price),resell(_resell),profit(_profit){} }; struct Deq { int s[100002],l,r; void clear(){l=0,r=-1;} void push_back(const int v){s[++r]=v;} void pop_back(){r--;} // void pop_front(){l++;}//這個不會用到 int size(){return r-l+1;} int operator[](const int i)const{return s[l+i];} int back(const int i){return s[r-i];} }DEQ; vector<Machine>MACHINE; LL DP[100002]; LL GetA(const int i){return MACHINE[i].profit;} LL GetB(const int i) { const Machine &m=MACHINE[i]; return DP[i]+(m.resell-m.price)-m.profit*m.day; } double GetX(const int i1,const int i2){assert(GetA(i1)!=GetA(i2));return (double)(GetB(i1)-GetB(i2))/(GetA(i2)-GetA(i1));} int N,D; void Query(const vector<int>&machines) { if((int)machines.size()==1)return; assert(!machines.empty()); const int half=(int)machines.size()/2; vector<int>left_machines,right_machines; for(int i=0;i<(int)machines.size();i++)(i<half?left_machines:right_machines).push_back(machines[i]); Query(left_machines); sort(left_machines.begin(),left_machines.end(),[](const int a,const int b)->bool{return GetA(a)<GetA(b);}); DEQ.clear(); const auto &lm=left_machines; for(int i=0;i<(int)lm.size();i++) { if(DP[lm[i]]<MACHINE[lm[i]].price)continue;//買不起這台機器 if(DEQ.size()&&GetA(DEQ.back(0))==GetA(lm[i]))//注意斜率相同的情況 { if(GetB(DEQ.back(0))<=GetB(lm[i]))DEQ.pop_back(); else continue; } while(DEQ.size()>=2&&GetX(DEQ.back(0),lm[i])<=GetX(DEQ.back(1),DEQ.back(0)))DEQ.pop_back(); DEQ.push_back(lm[i]); } if(DEQ.size()) { for(int i=2;i<DEQ.size();i++)assert(GetX(DEQ[i-2],DEQ[i-1])<=GetX(DEQ[i-1],DEQ[i])); const auto &rm=right_machines; for(int i=0,j=0;j<(int)rm.size();i++) { for(;j<(int)rm.size()&&(i+1==DEQ.size()||GetX(DEQ[i],DEQ[i+1])>=MACHINE[rm[j]].day-1LL);j++) getmax(DP[rm[j]],GetA(DEQ[i])*(MACHINE[rm[j]].day-1LL)+GetB(DEQ[i])); } } Query(right_machines); } int main() { // freopen("in.txt","r",stdin); // freopen("out.txt","w",stdout); while(scanf("%d%lld%d",&N,&DP[0],&D)==3) { if(N==0&&DP[0]==0LL&&D==0)break; MACHINE.clear(),MACHINE.push_back(Machine(0LL,0LL,0LL,0LL)); for(int i=1,d,p,r,g;i<=N;i++)assert(scanf("%d%d%d%d",&d,&p,&r,&g)==4),MACHINE.push_back(Machine(d,p,r,g)); for(int i=1;i<=N+1;i++)DP[i]=DP[0]; MACHINE.push_back(Machine(D+1LL,0LL,0LL,0LL)); sort(MACHINE.begin(),MACHINE.end(),[](const Machine &a,const Machine &b)->bool{return a.day<b.day;}); assert(MACHINE[0].day==0LL);assert(MACHINE[N+1].day==D+1LL); assert((int)MACHINE.size()==N+2); vector<int>machines; for(int i=0;i<=N+1;i++)machines.push_back(i); Query(machines); static int kase=1; printf("Case %d: %lld\n",kase++,DP[N+1]); } return 0; }
沒有留言:
張貼留言
歡迎留言或問問題~
若您的留言中包含程式碼,請參考這篇
如果留言不見了請別慌,那是因為被google誤判成垃圾留言,小莫會盡快將其手動還原
注意:只有此網誌的成員可以留言。