斜率/凸包板子

先开个板子博再说。

除了计算几何,其他板块中的“凸包”都指“凸壳”。

计算几何中的凸包维护

开始以为专题里“凸包”指的是计算几何,就先把板子写了。结果题全是斜率优化。。

两个向量 & 表示点积, * 表示叉积。

求二维凸包用的是 \(Andrew\) 算法,把凸包分成两个凸壳维护。将点按坐标排序后每次检查当前点是否在当前栈顶线段的右侧。如果在右侧则当前点优于栈顶,弹栈。

判断左右关系可以用叉积。正着做一遍倒着做一遍就可以得到一个完整的凸包。

洛谷P2742[模板]二维凸包
#include<bits/stdc++.h>
using namespace std;

namespace IO{
    typedef double DB;
    typedef long long LL;
    LL read(){
        LL x=0,f=0; char ch=getchar();
        while(ch>'9'||ch<'0'){ f|=(ch=='-'); ch=getchar(); }
        while(ch>='0'&&ch<='9'){ x=(x<<1)+(x<<3)+(ch^48); ch=getchar(); }
        return f?-x:x;
    } char output[50];
    void write(LL x,char sp){
        int len=0;
        if(x<0) putchar('-'), x=-x;
        do{ output[len++]=x%10+'0'; x/=10; }while(x);
        for(int i=len-1;~i;i--) putchar(output[i]); putchar(sp);
    }
    void ckmax(int& x,int y){ x=x>y?x:y; }
    void ckmin(int& x,int y){ x=x<y?x:y; }
} using namespace IO;

const int NN=100010;
int n,siz;
DB ans;

namespace Geometry{
    #define pot vec
    const DB eps=1e-8;
    struct vec{
        DB x,y;
        vec(){ x=y=0; }
        vec(DB _x){ x=_x; y=0; }
        vec(DB _x,DB _y){ x=_x; y=_y; }
        vec operator-()const{ return vec(-x,-y); }
        vec operator+(const vec& t)const{ return vec(x+t.x,y+t.y); }
        vec operator-(const vec& t)const{ return vec(x-t.x,y-t.y); }
        vec operator*(const DB&  t)const{ return vec(x*t,y*t); }
        vec operator/(const DB&  t)const{ return vec(x/t,y/t); }
        DB  operator&(const vec& t)const{ return x*t.x+y*t.y; }
        DB  operator*(const vec& t)const{ return x*t.y-y*t.x; }
    }p[NN],hul[NN];
    void read(vec& t){ scanf("%lf%lf",&t.x,&t.y); }
    bool cmpx(vec a,vec b){ return a.x!=b.x?(a.x<b.x):(a.y<b.y); }
    bool left(pot fst,pot snd,pot nxt){ return (snd-fst)*(nxt-fst)>0; }
    DB dis(pot x,pot y){ return sqrt((x.x-y.x)*(x.x-y.x)+(x.y-y.y)*(x.y-y.y)); }
    int gethull(int top=1,int lim=0){
        sort(p+1,p+n+1,cmpx);
        hul[0]=p[1]; hul[1]=p[2];
        for(int i=3;i<=n;i++){
            while(top>lim&&!left(hul[top-1],hul[top],p[i])) --top;
            hul[++top]=p[i];
        }
        lim=top; hul[++top]=p[n-1];
        for(int i=n-2;i;i--){
            while(top>lim&&!left(hul[top-1],hul[top],p[i])) --top;
            hul[++top]=p[i];
        }
        return top;
    }
} using namespace Geometry;

signed main(){
    n=read();
    for(int i=1;i<=n;i++) read(p[i]);
    if(n==1) return puts("0"),0;
    if(n==2) return printf("%.2lf\n",dis(p[1],p[2])),0;
    siz=gethull();
    for(int i=1;i<=siz;i++) ans+=dis(hul[i-1],hul[i]);
    printf("%.2lf\n",ans);
    return 0;
}

李超线段树

虽然好像不是凸包,但跟斜率相关主要是无脑且好用,还是提一下。很多斜率优化的题整出来式子后都可以直接上李超树。下面很多维护凸包的方式实际上也能用李超树代替。

只要没什么太毒瘤的限制一般斜率优化都可以用,平时的斜率优化式子交换 \(y\) 与纵截距 \(b\) 之后就能套到李超树里。

好像可以区间查,但目前还不会···

区间插线段两个 \(\log\) ,如果插直线则为一个。

[NOI2007]货币兑换
#include<bits/stdc++.h>
using namespace std;

namespace IO{
    typedef double DB;
    typedef long long LL;
    typedef pair<int,int>PII;
    LL read(){
        LL x=0,f=0; char ch=getchar();
        while(ch>'9'||ch<'0'){ f|=(ch=='-'); ch=getchar(); }
        while(ch>='0'&&ch<='9'){ x=(x<<1)+(x<<3)+(ch^48); ch=getchar(); }
        return f?-x:x;
    } char output[50];
    void write(LL x,char sp){
        int len=0;
        if(x<0) putchar('-'), x=-x;
        do{ output[len++]=x%10+'0'; x/=10; }while(x);
        for(int i=len-1;~i;i--) putchar(output[i]); putchar(sp);
    }
    void ckmax(LL& x,LL y){ x=x>y?x:y; }
    void ckmin(LL& x,LL y){ x=x<y?x:y; }
} using namespace IO;

const int NN=200010,TN=NN<<2;
int n;
DB f,h[NN],a[NN],b[NN],r[NN];

namespace LC_SegmentTree{
    #define ld rt<<1
    #define rd (rt<<1)|1
    struct seg{
        DB k,b;
        seg(){} seg(DB _k,DB _b){ k=_k; b=_b; }
        DB val(DB x){ return x*k+b; }
    }s[TN];
    void build(int rt=1,int l=1,int r=n){
        s[rt]=seg(0,-1e18);
        if(l==r) return;
        int mid=l+r>>1;
        build(ld,l,mid); build(rd,mid+1,r);
    }
    void insert(seg v,int opl=1,int opr=n,int rt=1,int l=1,int r=n){
        int mid=l+r>>1;
        if(l==r){ if(s[rt].val(h[l])<v.val(h[l])) s[rt]=v; return; }
        if(l>=opl&&r<=opr){
            if(s[rt].val(h[l])<=v.val(h[l])&&s[rt].val(h[r])<=v.val(h[r])){ s[rt]=v; return; }
            if(s[rt].val(h[l])>=v.val(h[l])&&s[rt].val(h[r])>=v.val(h[r])) return;
            if(s[rt].val(h[mid])<v.val(h[mid])) swap(s[rt],v);
            if(s[rt].val(h[l])<v.val(h[l])) insert(v,opl,opr,ld,l,mid);
            else insert(v,opl,opr,rd,mid+1,r);
            return;
        }
        if(opl<=mid) insert(v,opl,opr,ld,l,mid);
        if(opr>mid) insert(v,opl,opr,rd,mid+1,r);
    }
    DB query(int pos,int rt=1,int l=1,int r=n){
        DB res=s[rt].val(h[pos]);
        int mid=l+r>>1;
        if(l==r) return res;
        if(pos<=mid) return max(res,query(pos,ld,l,mid));
        else return max(res,query(pos,rd,mid+1,r));
    }
} using namespace LC_SegmentTree;

signed main(){
    n=read(); f=read();
    for(int i=1;i<=n;i++){
        scanf("%lf%lf%lf",&a[i],&b[i],&r[i]);
        h[i]=a[i]/b[i];
    }
    sort(h+1,h+n+1); build();
    for(int i=1;i<=n;i++){
        int pos=lower_bound(h+1,h+n+1,a[i]/b[i])-h;
        f=max(f,b[i]*query(pos));
        DB x=f*r[i]/(a[i]*r[i]+b[i]),y=f/(a[i]*r[i]+b[i]);
        insert(seg(x,y));
    }
    printf("%.3lf\n",f);
    return 0;
}

线段树维护凸包

对于一些带有区间查询,且需要斜率优化的题目,通常都可以用线段树维护凸包解决。

如果题目可以离线,可以将所有待插入的点坐标排序后依次插进线段树。不然可以等线段树区间内所有位置都存在点之后再 pushup 来保证复杂度,因为不满的区间一定不会做贡献。

两个凸包合并后形成的凸包一定都是原来凸包上的点,所以 pushup 时直接把左右儿子区间的点全拿出来求一遍凸包即可。

[SDOI2014]向量集
#include<bits/stdc++.h>
using namespace std;

namespace IO{
    #define x first
    #define y second
    typedef double DB;
    typedef long long LL;
    typedef pair<LL,LL>PII;
    LL read(){
        LL x=0,f=0; char ch=getchar();
        while(ch>'9'||ch<'0'){ f|=(ch=='-'); ch=getchar(); }
        while(ch>='0'&&ch<='9'){ x=(x<<1)+(x<<3)+(ch^48); ch=getchar(); }
        return f?-x:x;
    } char output[50];
    void write(LL x,char sp){
        int len=0;
        if(x<0) putchar('-'), x=-x;
        do{ output[len++]=x%10+'0'; x/=10; }while(x);
        for(int i=len-1;~i;i--) putchar(output[i]); putchar(sp);
    }
    void ckmax(LL& x,LL y){ x=x>y?x:y; }
    void ckmin(LL& x,LL y){ x=x<y?x:y; }
} using namespace IO;

const int NN=400010,TN=NN<<2;
int n,f,cnt;
LL ans;
char op;

namespace SegmentTree{
    #define ld rt<<1
    #define rd (rt<<1)|1
    #define fst(x) x.back()
    #define snd(x) x[x.size()-2]
    vector<PII>tmp,hu[TN],hd[TN];
    int siz[TN],len[TN];
    LL calc(int rt,PII v){
        if(!v.y) return max(v.x*hu[rt][0].x,v.x*fst(hu[rt]).x);
        if(v.y>0){
            int l=1,r=hu[rt].size()-1,res=0;
            while(l<=r){
                int mid=l+r>>1;
                if((hu[rt][mid].y-hu[rt][mid-1].y)*v.y+
                   (hu[rt][mid].x-hu[rt][mid-1].x)*v.x>=0) res=mid,l=mid+1;
                else r=mid-1;
            }
            return hu[rt][res].x*v.x+hu[rt][res].y*v.y;
        } else{
            int l=1,r=hd[rt].size()-1,res=0;
            while(l<=r){
                int mid=l+r>>1;
                if((hd[rt][mid].y-hd[rt][mid-1].y)*v.y+
                   (hd[rt][mid].x-hd[rt][mid-1].x)*v.x>=0) res=mid,l=mid+1;
                else r=mid-1;
            }
            return hd[rt][res].x*v.x+hd[rt][res].y*v.y;
        }
    }
    void pushup(int rt){
        if(siz[rt]^len[rt]) return;
        tmp.clear();
        for(auto x:hu[ld]) tmp.push_back(x);
        for(auto x:hu[rd]) tmp.push_back(x);
        sort(tmp.begin(),tmp.end());
        for(auto x:tmp){
            while(hu[rt].size()>1&&(fst(hu[rt]).y-snd(hu[rt]).y)*(x.x-fst(hu[rt]).x)<=
                                    (fst(hu[rt]).x-snd(hu[rt]).x)*(x.y-fst(hu[rt]).y)) hu[rt].pop_back();
            hu[rt].push_back(x);
        }
        tmp.clear();
        for(auto x:hd[ld]) tmp.push_back(x);
        for(auto x:hd[rd]) tmp.push_back(x);
        sort(tmp.begin(),tmp.end());
        for(auto x:tmp){
            while(hd[rt].size()>1&&(fst(hd[rt]).y-snd(hd[rt]).y)*(x.x-fst(hd[rt]).x)>=
                                    (fst(hd[rt]).x-snd(hd[rt]).x)*(x.y-fst(hd[rt]).y)) hd[rt].pop_back();
            hd[rt].push_back(x);
        }
    }
    void build(int rt=1,int l=1,int r=n){
        len[rt]=r-l+1;
        if(l==r) return;
        int mid=l+r>>1;
        build(ld,l,mid); build(rd,mid+1,r);
    }
    void insert(int pos,PII v,int rt=1,int l=1,int r=n){
        ++siz[rt];
        if(l==r) return hu[rt].push_back(v),hd[rt].push_back(v);
        int mid=l+r>>1;
        if(pos<=mid) insert(pos,v,ld,l,mid);
        else insert(pos,v,rd,mid+1,r);
        pushup(rt);
    }
    LL query(int opl,int opr,PII v,int rt=1,int l=1,int r=n){
        if(l>=opl&&r<=opr) return calc(rt,v);
        int mid=l+r>>1; LL res=-1e18;
        if(opl<=mid) ckmax(res,query(opl,opr,v,ld,l,mid));
        if(opr>mid) ckmax(res,query(opl,opr,v,rd,mid+1,r));
        return res;
    }
} using namespace SegmentTree;

signed main(){
    n=read(); cin>>op; f=(op!='E');
    build();
    for(int t=1;t<=n;t++){
        op=getchar(); while((op^'A')&&(op^'Q')) op=getchar();
        if(op=='A'){
            LL x=read()^(f?(ans&0x7fffffff):0);
            LL y=read()^(f?(ans&0x7fffffff):0);
            insert(++cnt,{x,y});
        } else{
            LL x=read()^(f?(ans&0x7fffffff):0);
            LL y=read()^(f?(ans&0x7fffffff):0);
            int l=read()^(f?(ans&0x7fffffff):0);
            int r=read()^(f?(ans&0x7fffffff):0);
            write(ans=query(l,r,{x,y}),'\n');
        }
    }
    return 0;
}

\(\texttt{set}\) 维护凸包

当凸包需要动态维护(加点)时,需要平衡树维护凸包。可以用 set 维护凸包简化代码。

每次加点时向左右分别扩展,检查左右的点是否需要被删去,最后检查当前点是否在凸包上。

[HAOI2011]防线修建
#include<bits/stdc++.h>
using namespace std;

namespace IO{
    #define x first
    #define y second
    typedef double DB;
    typedef long long LL;
    typedef pair<int,int>PII;
    LL read(){
        LL x=0,f=0; char ch=getchar();
        while(ch>'9'||ch<'0'){ f|=(ch=='-'); ch=getchar(); }
        while(ch>='0'&&ch<='9'){ x=(x<<1)+(x<<3)+(ch^48); ch=getchar(); }
        return f?-x:x;
    } char output[50];
    void write(LL x,char sp){
        int len=0;
        if(x<0) putchar('-'), x=-x;
        do{ output[len++]=x%10+'0'; x/=10; }while(x);
        for(int i=len-1;~i;i--) putchar(output[i]); putchar(sp);
    }
    void ckmax(LL& x,LL y){ x=x>y?x:y; }
    void ckmin(LL& x,LL y){ x=x<y?x:y; }
} using namespace IO;

const int NN=200010;
int n,m,q;
PII p[NN];
bool ban[NN];
DB now,ans[NN];
struct opt{ int tp,id; }o[NN];
DB dis(PII a,PII b){ return sqrt(pow(a.x-b.x,2)+pow(a.y-b.y,2)); }

namespace Hull{
    typedef set<PII>::iterator sit;
    set<PII>s;
    bool cmp(PII a,PII b,PII c){
        int x=(b.y-a.y)*(c.x-b.x);
        int y=(b.x-a.x)*(c.y-b.y);
        return x>y;
    }
    void del(sit v){
        sit l=--v; ++v; sit r=++v; --v;
        now+=dis(*l,*r)-dis(*v,*r)-dis(*v,*l);
        s.erase(v);
    }
    void add(PII u){
        s.insert(u); sit v=s.find(u);
        sit l=--v; ++v; sit r=++v; --v;
        now+=dis(*v,*l)+dis(*v,*r)-dis(*l,*r);
    }
    void insert(PII v){
        sit p=s.lower_bound(v);
        if(p!=s.end()&&p->x==v.x)
            if(p->y>=v.y) return;
            else del(p);
        while(!s.empty()){
            p=s.lower_bound(v);
            if(p==s.begin()||(--p)==s.begin()) break;
            sit q=p; --p;
            if(!cmp(*p,*q,v)) del(q);
            else break;
        }
        while(!s.empty()){
            p=s.lower_bound(v);
            if(p==s.end()||p==(--s.end())) break;
            sit q=p; ++p;
            if(!cmp(v,*q,*p)) del(q);
            else break;
        }
        p=s.lower_bound(v);
        sit q=p; --p;
        if(cmp(*p,v,*q)) add(v);
    }
} using namespace Hull;

signed main(){
    n=read();
    p[1]={read(),0}; p[1].y=read();
    m=read()+1; s.insert({0,0}); s.insert({n,0}); now=n;
    for(int i=2;i<=m;i++) p[i]={read(),0}, p[i].y=read();
    q=read();
    for(int i=1;i<=q;i++){
        o[i].tp=read();
        if(o[i].tp==1) ban[o[i].id=read()+1]=1;
    }
    for(int i=1;i<=m;i++) if(!ban[i]) insert(p[i]);
    for(int i=q;i;i--)
        if(o[i].tp==1) insert(p[o[i].id]);
        else ans[i]=now;
    for(int i=1;i<=q;i++) if(o[i].tp==2) printf("%.2lf\n",ans[i]);
    return 0;
}

CDQ分治进行斜率优化

在斜率优化时会遇到动态维护凸包的问题。这时除了使用 set 维护凸包外,也可以用CDQ分治解决。

具体流程:现将所有元素按斜率 \(k\) 排序,然后每次分治时将时间在前一半的元素放到左区间,递归左区间。

处理好左区间后解决左区间对右区间的贡献。对左区间建出凸包,现在因为右区间斜率单调,可以省去凸包上二分。

因为要建凸包,所以处理完当前区间后按横坐标 \(x\) 递增归并排序。

[NOI2007]货币兑换
#include<bits/stdc++.h>
using namespace std;

namespace IO{
    typedef double DB;
    typedef long long LL;
    typedef pair<int,int>PII;
    LL read(){
        LL x=0,f=0; char ch=getchar();
        while(ch>'9'||ch<'0'){ f|=(ch=='-'); ch=getchar(); }
        while(ch>='0'&&ch<='9'){ x=(x<<1)+(x<<3)+(ch^48); ch=getchar(); }
        return f?-x:x;
    } char output[50];
    void write(LL x,char sp){
        int len=0;
        if(x<0) putchar('-'), x=-x;
        do{ output[len++]=x%10+'0'; x/=10; }while(x);
        for(int i=len-1;~i;i--) putchar(output[i]); putchar(sp);
    }
    void ckmax(LL& x,LL y){ x=x>y?x:y; }
    void ckmin(LL& x,LL y){ x=x<y?x:y; }
} using namespace IO;

const int NN=200010,TN=NN<<2;
const DB eps=1e-8,inf=1e10;
int n,stk[NN];
struct data{
    DB k,x,y,a,b,r,f; int id;
    bool operator<(const data& t)const{ return k<t.k; }
}d[NN],tmp[NN];
DB ans;
DB slope(int i,int j){
    if(abs(d[i].x-d[j].x)<eps) return inf;
    return (d[i].y-d[j].y)/(d[i].x-d[j].x);
}

void merge_sort(int l,int r){
    int mid=l+r>>1,il=l,ir=mid+1;
    for(int i=l;i<=r;i++){
        if(il>mid) tmp[i]=d[ir++];
        else if(ir>r) tmp[i]=d[il++];
        else if(d[il].x<d[ir].x) tmp[i]=d[il++];
        else tmp[i]=d[ir++];}
    for(int i=l;i<=r;i++) d[i]=tmp[i];
}

void solve(int l,int r){
    if(l==r){
        d[l].f=max(d[l].f,ans); ans=max(ans,d[l].f);
        d[l].y=d[l].f/(d[l].a*d[l].r+d[l].b);
        d[l].x=d[l].y*d[l].r;
        return;
    }
    int mid=l+r>>1,il=l,ir=mid+1,top=0;
    for(int i=l;i<=r;i++)
        d[i].id<=mid?tmp[il++]=d[i]:tmp[ir++]=d[i];
    for(int i=l;i<=r;i++) d[i]=tmp[i];
    solve(l,mid);
    for(int i=l;i<=mid;i++){
        while(top>1&&slope(stk[top],i)+eps>slope(stk[top-1],stk[top])) --top;
        stk[++top]=i;
    }
    for(int i=mid+1;i<=r;i++){
        while(top>1&&slope(stk[top],stk[top-1])<=d[i].k+eps) --top;
        d[i].f=max(d[i].f,d[stk[top]].x*d[i].a+d[stk[top]].y*d[i].b);
    }
    solve(mid+1,r); merge_sort(l,r);
}

signed main(){
    n=read(); ans=read();
    for(int i=1;i<=n;i++){
        scanf("%lf%lf%lf",&d[i].a,&d[i].b,&d[i].r);
        d[i].k=-d[i].a/d[i].b; d[i].id=i;
    }
    sort(d+1,d+n+1); solve(1,n);
    printf("%.3lf\n",ans);
    return 0;
}

posted @ 2022-01-09 09:05  keen_z  阅读(75)  评论(0编辑  收藏  举报