懶人的福利?教你用set維護斜率優化凸包


斜率優化題目大家肯定都做得不少了,有一些題目查詢插入點的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 }
View Code

 

不得不說STL的set跑的還是挺快的。

 

這里是回檔后的世界,無論你做什么,你都一定會這樣做。

而我努力改變命運,只是為了防止那一切重現。

(來自某中二病晚期患者(不))


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM