Convex hull trick算法

来源:互联网 发布:nginx只允许域名访问 编辑:程序博客网 时间:2024/05/18 02:11

本文参考自http://wcipeg.com/wiki/Convex_hull_trick,内容包括我对wiki的一些简单翻译和个人的一些理解。

Convex hull trick是一种算法或者说数据结构,用于在一组线性函数(形如y=mi*x+bi)中,每次查询给以具体的x,可以快速求出最大/最小的y。

举个例子。

现在有y=4, y=4/3+2/3x, y=12-3x和 y=3-1/2x这四条直线,问当x取值为1时,这些线性函数的y里面的最小值。


四条直线如上图,可以发现当x=2时,直线y=4/3+2/3x有最小值,为2。

朴素的算法很容易就能想到,假设查询Q次,共有M条直线,那么时间复杂度就是O(QM)。

现在讲一下用Convex hull trick求最小值。

对于上面的几条直线,y=4始终不可能是最优解因此剔除,余下的只取它们为最小值的那段区间(上图绿色部分),它们的斜率是依次减小的,形成了一个凸包,它只有一个峰值。

因此,在去掉无关紧要的直线后,将余下的直线按照斜率降序排序,这样形成了N个区间,其中每段都是每条直线取最小值的那部分。如果我们可以确定每个区间的端点,那么这就变成了一个简单的问题:使用二分查询每个答案。

在前面已经发现,如果相关的线段已经确定和排序,那很明显它可以在O(lgN)内使用二分求解。因此,如果我们一次添加一条直线在我们数据结构中,然后再维护这个结构,将得到一个可行的算法:开始没有线(也可能一条或两条,取决于具体实现细节),每次添加一条直线直到整个的数据结构完成。

假设在回答任何查询前我们能够处理所有直线。我们按照斜率降序排序,每次仅添加一条直线。当添加一条新的直线时,之前存在的一些直线可能被移除,因为它们不再有用。如果我们想象所有直线位于栈(stack)上,最先被添加的直线位于在栈顶上。当我们添加一个新的直线时,考虑栈顶的直线是否还有用。如果是,我们加入新的直线并继续;如果不是,则弹出栈顶并重复这个过程直到栈顶的直线不应被丢弃或只有一条直线(底部的这个可能永远不会被删除)。
那么,我们如何才能确定是否应该从堆栈中弹出?假设l_1,l_2,和l_3分别是栈顶方向的第二条直线、栈顶的直线和待添加的直线。然后,当且仅当l_1和l_3交点位于l_1和l_2交点的左侧时,l_2无用可以被删除。(这是因为它意味着l_3已经包含l_2以前的部分。)

Convex hull trick空间复杂度为O(M),算法过程包括排序O(MlgM),Q次查询,每次是O(lgM),故时间复杂度是O((M+Q)lgM)。

最后贴一个模版。跟前面的例子不一样,这里是用来求最大值的,外部使用的时候直线要按斜率升序添加。要改成求最小值也很容易:二分改成求最小值,另外外部使用的时候直线要按降序依次添加。

struct Line{    LL m,b;    LL get(LL x)    {        return m*x+b;    }};struct ConvexHull{    int size;    vector<Line> hull;    ConvexHull(int maxSize)    {        hull=vector<Line>(maxSize+1);        size=0;    }    bool isBad(int l1,int l2,int l3)    {        double left=1.0*(hull[l3].b-hull[l1].b)/(hull[l1].m-hull[l3].m);        double right=1.0*(hull[l1].b-hull[l2].b)/(hull[l2].m-hull[l1].m);        return left<right;    }    void addLine(LL m,LL b)    {        hull[size++]=Line{m,b};        while(size>2&&isBad(size-3,size-2,size-1))        {            hull[size-2]=hull[size-1];            --size;        }    }    LL query(LL x)    {        int l=0,r=size-1;        while(l<r)        {            int m=(l+r)/2;            if(hull[m].get(x)<=hull[m+1].get(x))                l=m+1;            else                r=m;        }        return hull[l].get(x);    }};


0 0
原创粉丝点击