斜率优化题目大家肯定都做得不少了,有一些题目查询插入点的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() {
  4. lst = nxt , ++nxt;
  5. if( nxt == st.end() ) return;
  6. if( (*lst-p) * (*nxt-*lst) <= ) 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) <= ) break;
  15. st.erase(lst);
  16. }
  17. }
  18. inline void insert(const Point &p) {
  19. cmp = ;
  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 = ;
  3. const double k = -a[id] / b[id];
  4. set<Point>::iterator it = st.lower_bound((Point){,,k}); // it can't be st.end() if st isn't empty .
  5. if( it==st.end() ) return ;
  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() {
  31. lst = nxt , ++nxt;
  32. if( nxt == st.end() ) return;
  33. if( (*lst-p) * (*nxt-*lst) <= ) 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) <= ) break;
  42. st.erase(lst);
  43. }
  44. }
  45. inline void insert(const Point &p) {
  46. cmp = ;
  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 = ;
  71. const double k = -a[id] / b[id];
  72. set<Point>::iterator it = st.lower_bound((Point){,,k}); // it can't be st.end() if st isn't empty .
  73. if( it==st.end() ) return ;
  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=;i<=n;i++) scanf("%lf%lf%lf",a+i,b+i,rate+i);
  86. for(int i=;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],});
  90. }
  91. printf("%0.3lf\n",ans);
  92. return ;
  93. }

不得不说STL的set跑的还是挺快的。

这里是回档后的世界,无论你做什么,你都一定会这样做。

而我努力改变命运,只是为了防止那一切重现。

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

懒人的福利?教你用set维护斜率优化凸包的更多相关文章

  1. 网络编程懒人入门(八):手把手教你写基于TCP的Socket长连接

    本文原作者:“水晶虾饺”,原文由“玉刚说”写作平台提供写作赞助,原文版权归“玉刚说”微信公众号所有,即时通讯网收录时有改动. 1.引言 好多小白初次接触即时通讯(比如:IM或者消息推送应用)时,总是不 ...

  2. 【轮子狂魔】手把手教你用JS给博客动态增加目录 - 超级懒人版

    动态显示目录的作用 不用每次写博客的时候繁琐的人工整理目录,又可以动态浮动在右下角,方便快速跳到感兴趣的位置同时也可以快速的对文章内容有一个大概的了解. 实现原理 首先根据个人喜好,我习惯了用 h1 ...

  3. 懒人邮件群发日发50-100万封不打码不换IP不需发件箱大站协议系统营销软件100%进收件箱

    用一种新的技术思维去群发邮件 一种不用换IP,不需要任何发件箱的邮件群发方式 一种不需要验证码,不需要**代码变量的邮件群发方式 即使需要验证码也能全自动识别验证码的超级智能软件 教你最核心的邮件群发 ...

  4. Mac OS X 懒人版安装教程(之前的图全部挂了,所以重发了)

    请版主把我之前发的那个帖子删了!因为所有的图全部挂了,所以麻烦版主了…… 安装中出现五国的话就请进入这里看看是那里的错误http://bbs.pcbeta.com/viewthread-863656- ...

  5. 详细的OS X Yosemite 10.10懒人版安装教程

    永远记住一句话:难,是因为不会.先是要放宽心态,才更利于解决安装过程中这样那样的问题.多尝试多动脑,不要有过份的依赖.很多问题到解决以后,才发现是如此的简单,我装黑苹果是拿来使用的,所以我的目的是装好 ...

  6. 【原创】窥视懒人的秘密---android下拉刷新开启手势的新纪元

    小飒的成长史原创作品:窥视懒人的秘密---android下拉刷新开启手势的新纪元转载请注明出处 **************************************************** ...

  7. 网络编程懒人入门(九):通俗讲解,有了IP地址,为何还要用MAC地址?

    1.前言 标题虽然是为了解释有了 IP 地址,为什么还要用 MAC 地址,但是本文的重点在于理解为什么要有 IP 这样的东西.本文对读者的定位是知道 MAC 地址是什么,IP 地址是什么. (本文同步 ...

  8. FlytestingToolkit工具派送,懒人的测试思考

    工欲善其事必先利其器,在IT路上摸爬这些年,去年我们分享了<Fiddler录制jmeter脚本,干货分享>,今天我们有另外的思考,我懒,故我思考. 下载解压后是这样的: 双击 Flytes ...

  9. 网络编程懒人入门(十):一泡尿的时间,快速读懂QUIC协议

    1.TCP协议到底怎么了? 现时的互联网应用中,Web平台(准确地说是基于HTTP及其延伸协议的客户端/服务器应用)的数据传输都基于 TCP 协议. 但TCP 协议在创建连接之前需要进行三次握手(如下 ...

随机推荐

  1. HDU 1524 树上无环博弈 暴力SG

    一个拓扑结构的图,给定n个棋的位置,每次可以沿边走,不能操作者输. 已经给出了拓扑图了,对于每个棋子找一遍SG最后SG和就行了. /** @Date : 2017-10-13 20:08:45 * @ ...

  2. WebSlides - 轻松制作漂亮的 HTML 幻灯片(演讲稿)

    WebSlides 是一个开源的 HTML 幻灯片项目,能够帮助熟悉前端语言的开发者快速制作出效果精美的幻灯片.页面中的每个 <section> 都是一个独立的幻灯片,只需要很少的 CSS ...

  3. iOS 处理缓存的三种方法

    缓存处理是个相当头疼的事情,要根据需要综合应用不同的策略.总的来说有以下几种情况: 1.URL缓存,例如社交应用的帖子浏览,要在viewDidAppear:里面进行URL缓存.简单来说就是用NSURL ...

  4. 第12月第8天 Retrofit.builder

    1. retrofit = new Retrofit.Builder() .client(okHttpClient) .addConverterFactory(GsonConverterFactory ...

  5. mybatis多对多关联查询——(十)

    1.需求 查询用户及用户购买商品信息. 2     sql语句 查询主表是:用户表 关联表:由于用户和商品没有直接关联,通过订单和订单明细进行关联,所以关联表: orders.orderdetail. ...

  6. Linux驱动技术(五) _设备阻塞/非阻塞读写【转】

    转自:http://www.cnblogs.com/xiaojiang1025/p/6377925.html 等待队列是内核中实现进程调度的一个十分重要的数据结构,其任务是维护一个链表,链表中每一个节 ...

  7. Linux内核源码分析--内核启动之(4)Image内核启动(setup_arch函数)(Linux-3.0 ARMv7)【转】

    原文地址:Linux内核源码分析--内核启动之(4)Image内核启动(setup_arch函数)(Linux-3.0 ARMv7) 作者:tekkamanninja 转自:http://blog.c ...

  8. update-rc.d使用

    在Linux系统下,一个Services的启动.停止以及重启通常是通过/etc/init.d目录下的脚本来控制的.然而,在启动或改变运行级别时,是在/etc/rcX.d中来搜索脚本.其中X是运行级别的 ...

  9. Android Camera详解

    相关的类 android.hardware.camera2 Camera SurfaceView---这个类用于向用户呈现实时相机预览. MediaRecorder---这个类用于从摄像机录制视频. ...

  10. intellij 出现“Usage of API documented as @since 1.8+”的解决办法

    intellij 出现“Usage of API documented as @since 1.8+”的解决办法 Usage of API documented as @since 1.8+ This ...