广义李超线段树

前言:

1.本篇文章为李超线段树的扩展,不会的可以去这里

这是我的模板:

struct LCT
{
    int tot=0,rt=0;
    struct Tree{int lc,rc,id;}tr[M];
    ll Ans(ll z,ll x){return p[z].k*x+p[z].b;}
    #define ls (tr[p].lc)
    #define rs (tr[p].rc)
    #define mi ((l+r)>>1)
    void Upd(int &p,int l,int r,int u)
    {
        if(!p)p=++tot;
        int &v=tr[p].id;
        if(Ans(u,mi)>Ans(v,mi))swap(u,v);
        if(Ans(u,l)>Ans(v,l))Upd(ls,l,mi,u);
        if(Ans(u,r)>Ans(v,r))Upd(rs,mi+1,r,u);
    }
    ll Ask(ll p,ll l,ll r,ll d)
    {
        if(!p)return 0;
        ll ans=Ans(tr[p].id,d);
        if(l==r)return ans;
        return max(ans,d<=mi?Ask(ls,l,mi,d):Ask(rs,mi+1,r,d));
    }
    void Clear(int l,int r)
    {
        for(int i=1;i<=tot;i++)tr[i].lc=tr[i].rc=tr[i].id=0;
        tot=rt=0;
        for(int i=l;i<=r;i++)Upd(rt,0,n,i);
    }
}T[M];

 

2.此算法是本人在做题时yy出来的,不知道之前有没有出现过

算法:

1.背景:

李超线段树主要用来维护斜率优化题目,相较于维护凸壳,优点在于无脑简单无需分析,代码小清新,缺点在于有些时候时间复杂度会比维护凸壳高。

当题目不再是维护的函数不再是直线时,就只有单调队列/单调栈的做法。

此算法主要是解决维护非直线时的李超线段树做法。

2.实现:

注意到李超线段树中有一个函数 Ans(看模板) 是用来计算函数在一个点的取值。

我们如果将我们要求的函数(非直线)的解析式带入,就解决了问题。

例题1:

[POI2011] Lightning Conductor

把绝对值拆开,正反维护各维护一边

把 $a_j+\sqrt{i-j}$ 看成函数,套上李超线段树求最大值

注意到开根运算是有定义域的,一种无脑的做法就是插入到一段区间内。

巧妙的方法是重定义开根运算,如果遇到负数就设成负无穷。

为什么不会出 bug 呢?注意到询问单调递增且都比所有插入的 $j$ 都大,那么也就不会访问到开根负数的地方。

代码:

#include<bits/stdc++.h>
using namespace std;

typedef double db;
typedef long long ll;
const int N=5e5+3;
const db eps=1e-10;
int n,rt,a[N],sx[N],sy[N];
struct Fun{int h,g;}p[N];
struct LCT
{
    int tot=0,o=0;
    struct Tree{int lc,rc,id;}tr[N];
    int dcmp(db x,db y){return x-y>eps?1:y-x>eps?-1:0;}
    db sqr(db x){return dcmp(0,x)==1?-1e9:sqrt(x);}
    db Ans(int id,int x){return (db)p[id].h+sqr((!o)?x-p[id].g:p[id].g-x);}
    #define ls (tr[p].lc)
    #define rs (tr[p].rc) 
    #define mi ((l+r)>>1)
    void Upd(int &p,int l,int r,int u)
    {
        if(!p){p=++tot;tr[p].id=u;return;}
        int &v=tr[p].id;
        if(dcmp(Ans(u,mi),Ans(v,mi))==1)swap(u,v);
        if(dcmp(Ans(u,l),Ans(v,l))==1)Upd(ls,l,mi,u);
        if(dcmp(Ans(u,r),Ans(v,r))==1)Upd(rs,mi+1,r,u);
    }
    db Ask(int p,int l,int r,int d)
    {
        if(!p)return 0;
        db ans=Ans(tr[p].id,d);
        if(l==r)return ans;
        return max(ans,d<=mi?Ask(ls,l,mi,d):Ask(rs,mi+1,r,d));
    }
    void Clear()
    {
        for(int i=1;i<=tot;i++)tr[i].lc=tr[i].rc=tr[i].id=0;
        tot=rt=0;o=1;
    }
}T;
int main()
{
    cin>>n;
    for(int i=1;i<=n;i++)cin>>a[i],p[i]={a[i],i};
    for(int i=1;i<=n;i++)sx[i]=ceil(T.Ask(1,1,n,i)),T.Upd(rt,1,n,i);
    T.Clear();
    for(int i=n;i>=1;i--)sy[i]=ceil(T.Ask(1,1,n,i)),T.Upd(rt,1,n,i);
    for(int i=1;i<=n;i++)cout<<max(max(sx[i],sy[i])-a[i],0)<<endl;
}
View Code

例题二:

https://www.luogu.com.cn/problem/P1912

把 $|s_i-s_j-1-len|^p$ 看成函数,套李超线段树。

问题在这个函数的值会非常大,但我们又需要比大小,所以只能请出 long double来解决。

代码:

#include<bits/stdc++.h>
using namespace std;

typedef __int128 ll;
typedef long double ld;
typedef pair<ll,ll> pll;
#define fi first
#define se second 
const ll N=3e6+3,INF=(ll)1e18;
ll n,Len,P,rt,sum[N],sta[N];
pll f[N];
string str[N];
ll read()
{
    ll x=0,f=1;char c=getchar();
    while(c<'0'||c>'9'){if(c=='-')f=-1;c=getchar();}
    while(c>='0'&&c<='9'){x=(x<<1)+(x<<3)+(c^48);c=getchar();}
    return x*f;
}
void write(ll X)
{
    if(X<0){putchar('-');X=~(X-1);}int s[20],o=0;
    while(X){s[++o]=X%10;X/=10;}
    if(!o)s[++o]=0;while(o)putchar(s[o--]+'0');
    putchar('\n');
}
struct LCT
{
    ll tot=0;
    struct Tree{ll lc,rc,id;}tr[N];
    ll Abs(ll x){return x>=0?x:-x;}
    ld Ksm(ld x,ll y){ld s=1;for(ll i=1;i<=y;i<<=1,x=x*x)if(y&i)s=s*x;return s;}
    ld Ans(ll id,ll x){return f[id].fi+Ksm(Abs(x-sum[id]-1-Len),P);}
    ll Res(ll id,ll x){return min((ld)INF+1,Ans(id,x));}
    #define ls (tr[p].lc)
    #define rs (tr[p].rc)
    #define mi ((l+r)>>1)
    void Upd(ll &p,ll l,ll r,ll u)
    {
        if(!p){p=++tot;tr[p].id=u;return;}
        ll &v=tr[p].id;
        if(Ans(u,mi)<Ans(v,mi))swap(u,v);
        if(Ans(u,l)<Ans(v,l))Upd(ls,l,mi,u);
        if(Ans(u,r)<Ans(v,r))Upd(rs,mi+1,r,u);
    }
    pll Ask(ll p,ll l,ll r,ll d)
    {
        if(!p)return {INF+1,0};
        pll ans={Res(tr[p].id,d),tr[p].id};
        if(l==r)return ans;
        return min(ans,d<=mi?Ask(ls,l,mi,d):Ask(rs,mi+1,r,d));
    }
    void Clear()
    {
        for(int i=1;i<=tot;i++)tr[i].lc=tr[i].rc=tr[i].id=0;
        tot=rt=0;
    }
}T;
void Solve()
{
    n=read();Len=read();P=read();T.Clear();
    for(int i=1;i<=n;i++)cin>>str[i],sum[i]=sum[i-1]+str[i].size();
    for(int i=1;i<=n;i++)sum[i]+=i;
    T.Upd(rt,0,sum[n],0);
    for(int i=1;i<=n;i++)f[i]=T.Ask(rt,0,sum[n],sum[i]),T.Upd(rt,0,sum[n],i);
    if(f[n].fi>INF)cout<<"Too hard to arrange"<<endl;
    else
    {
        write(f[n].fi);ll top=0,x=n;
        while(x)sta[++top]=x,x=f[x].se;
        sta[++top]=0;
        for(int i=top;i>1;i--)
        {
            for(int j=sta[i]+1;j<sta[i-1];j++)cout<<str[j]<<" ";
            cout<<str[sta[i-1]]<<endl;
        }
    }
    cout<<"--------------------"<<endl;
}
int main()
{
    ll T;T=read();
    while(T--)Solve();
}
View Code

 3.使用条件

必须能快速求出函数在某一点的取值,任意两个函数只能有一个交点。

posted @ 2023-09-22 13:28  Hanghang007  阅读(395)  评论(2编辑  收藏  举报