斜率優化題目大家肯定都做得不少了,有一些題目查詢插入點的x坐標和查詢斜率都不單調,這樣就需要維護動態凸包並二分斜率。(例如bzoj1492)
常規的做法是cdq分治或手寫平衡樹維護凸包,然而如果我不願意寫分治,也懶得打平衡樹,怎么辦呢?
沒關系,今天我告訴你怎么用一個set維護這種凸包。
首先orzLH,沒什么特殊意義,只是單純的orz。
我們定義f[i]表示在第i天能擁有的金券組數,按照第i天的比例。
那么,我們要把前面的金券在今天賣出獲得最多的錢,並在今天進行買入。
所以,f[i]=max((f[j]*a[i]+f[j]/rate[j]*b[i])/(a[i]+rate[i]*b[i]))。
除下去的東西是一個常數,扔掉。
然后我們就有:
t=max(a[i]*(f[j])+b[i]*(f[j]/rate[j]))。
如果我們把f[j]看做x,f[j]/rate[j]看做y,我們有:
t=a*x+b*y,兩邊同時除以b,得到:
t/b=(a/b)x+y
y=(t/b)-(a/b)*x
好的,現在我們有一條斜率為-(a/b)的直線,要找一個點使之截距最大。
這樣我們維護一個右上1/4凸殼即可。
怎么維護?
我們考慮不用斜率優化,單純水平序維護凸包,那么點是按照x坐標單增在平衡樹上排列的。
現在我們在每個點維護他與后面點連線斜率,我們會發現:這個斜率是單降的。
所以,我們可以通過適當地轉換cmp函數,來通過一個set完成兩種比較。
我們定義:
1 int cmp; // 0 compare x , 1 compare slope . 2 struct Point { 3 double x,y,slop; 4 friend bool operator < (const Point &a,const Point &b) { 5 if( !cmp ) return a.x != b.x ? a.x < b.x : a.y < b.y; 6 return a.slop > b.slop; 7 } 8 friend Point operator - (const Point &a,const Point &b) { 9 return (Point){a.x-b.x,a.y-b.y}; 10 } 11 friend double operator * (const Point &a,const Point &b) { 12 return a.x * b.y - b.x * a.y; 13 } 14 inline double calc(double a,double b) const { 15 return a * x + b * y; 16 } 17 }; 18 set<Point> st;
插入就是正常凸包插入,最后再維護一下斜率就行了。注意彈出左邊后迭代器會失效,所以需要重新lower_bound一下。(可能原來你的迭代器是原來的end,結果彈出左邊后end改變了,兩個end不同,然后你去彈出右邊,訪問無效迭代器,就直接RE了)
1 inline void Pop_right(set<Point>::iterator nxt,const Point &p) { 2 set<Point>::iterator lst; 3 while(1) { 4 lst = nxt , ++nxt; 5 if( nxt == st.end() ) return; 6 if( (*lst-p) * (*nxt-*lst) <= 0 ) return; 7 st.erase(lst); 8 } 9 } 10 inline void Pop_left(set<Point>::iterator prv,const Point &p) { 11 set<Point>::iterator lst; 12 while(prv!=st.begin()) { 13 lst = prv , --prv; 14 if( (*lst-*prv) * (p-*lst) <= 0 ) break; 15 st.erase(lst); 16 } 17 } 18 inline void insert(const Point &p) { 19 cmp = 0; 20 set<Point>::iterator prv,nxt,lst=st.lower_bound(p); 21 if(lst!=st.begin()) Pop_left(--lst,p); 22 lst=st.lower_bound(p); 23 if(lst!=st.end()) Pop_right(lst,p); 24 st.insert(p) , lst = st.find(p); 25 if(lst!=st.begin()) { 26 prv = lst , --prv; 27 Point x = *prv; 28 x.slop = ( p.y - x.y ) / ( p.x - x.x ); 29 st.erase(prv) , st.insert(x); 30 } 31 nxt = lst , ++nxt; 32 if(nxt!=st.end()) { 33 Point x = p , n = *nxt; 34 x.slop = ( n.y - x.y ) / ( n.x - x.x ); 35 st.erase(lst) , st.insert(x); 36 } else { 37 Point x = p; 38 x.slop = -1e18; 39 st.erase(lst) , st.insert(x); 40 } 41 }
查詢的話就更改一下比較函數,然后特判一下邊界防止RE就好。
1 inline double query(const int id) { 2 cmp = 1; 3 const double k = -a[id] / b[id]; 4 set<Point>::iterator it = st.lower_bound((Point){0,0,k}); // it can't be st.end() if st isn't empty . 5 if( it==st.end() ) return 0; 6 double ret = it->calc(a[id],b[id]); 7 if( it != st.begin() ) { 8 --it; 9 ret = max( ret , it->calc(a[id],b[id]) ); 10 } 11 return ret; 12 }
所以整體代碼:
Bzoj1492:
1 #include<cstdio> 2 #include<algorithm> 3 #include<set> 4 #include<cmath> 5 using namespace std; 6 const int maxn=1e5+1e2; 7 8 int cmp; // 0 compare x , 1 compare slope . 9 struct Point { 10 double x,y,slop; 11 friend bool operator < (const Point &a,const Point &b) { 12 if( !cmp ) return a.x != b.x ? a.x < b.x : a.y < b.y; 13 return a.slop > b.slop; 14 } 15 friend Point operator - (const Point &a,const Point &b) { 16 return (Point){a.x-b.x,a.y-b.y}; 17 } 18 friend double operator * (const Point &a,const Point &b) { 19 return a.x * b.y - b.x * a.y; 20 } 21 inline double calc(double a,double b) const { 22 return a * x + b * y; 23 } 24 }; 25 set<Point> st; 26 double a[maxn],b[maxn],rate[maxn],f[maxn],ans; 27 28 inline void Pop_right(set<Point>::iterator nxt,const Point &p) { 29 set<Point>::iterator lst; 30 while(1) { 31 lst = nxt , ++nxt; 32 if( nxt == st.end() ) return; 33 if( (*lst-p) * (*nxt-*lst) <= 0 ) return; 34 st.erase(lst); 35 } 36 } 37 inline void Pop_left(set<Point>::iterator prv,const Point &p) { 38 set<Point>::iterator lst; 39 while(prv!=st.begin()) { 40 lst = prv , --prv; 41 if( (*lst-*prv) * (p-*lst) <= 0 ) break; 42 st.erase(lst); 43 } 44 } 45 inline void insert(const Point &p) { 46 cmp = 0; 47 set<Point>::iterator prv,nxt,lst=st.lower_bound(p); 48 if(lst!=st.begin()) Pop_left(--lst,p); 49 lst=st.lower_bound(p); 50 if(lst!=st.end()) Pop_right(lst,p); 51 st.insert(p) , lst = st.find(p); 52 if(lst!=st.begin()) { 53 prv = lst , --prv; 54 Point x = *prv; 55 x.slop = ( p.y - x.y ) / ( p.x - x.x ); 56 st.erase(prv) , st.insert(x); 57 } 58 nxt = lst , ++nxt; 59 if(nxt!=st.end()) { 60 Point x = p , n = *nxt; 61 x.slop = ( n.y - x.y ) / ( n.x - x.x ); 62 st.erase(lst) , st.insert(x); 63 } else { 64 Point x = p; 65 x.slop = -1e18; 66 st.erase(lst) , st.insert(x); 67 } 68 } 69 inline double query(const int id) { 70 cmp = 1; 71 const double k = -a[id] / b[id]; 72 set<Point>::iterator it = st.lower_bound((Point){0,0,k}); // it can't be st.end() if st isn't empty . 73 if( it==st.end() ) return 0; 74 double ret = it->calc(a[id],b[id]); 75 if( it != st.begin() ) { 76 --it; 77 ret = max( ret , it->calc(a[id],b[id]) ); 78 } 79 return ret; 80 } 81 82 int main() { 83 static int n; 84 scanf("%d%lf",&n,&ans); 85 for(int i=1;i<=n;i++) scanf("%lf%lf%lf",a+i,b+i,rate+i); 86 for(int i=1;i<=n;i++) { 87 ans = max( ans , query(i) ); 88 f[i] = ans * rate[i] / ( a[i] * rate[i] + b[i] ); 89 insert((Point){f[i],f[i]/rate[i],0}); 90 } 91 printf("%0.3lf\n",ans); 92 return 0; 93 }
不得不說STL的set跑的還是挺快的。
這里是回檔后的世界,無論你做什么,你都一定會這樣做。
而我努力改變命運,只是為了防止那一切重現。
(來自某中二病晚期患者(不))
