笔记 - 吉司机线段树

前几天换的小蓝花(左下角)是不是很可爱啊

吉司机线段树

简介

吉如一老师(吉司机)发明的线段树。复杂度基于势能分析,解决区间取min/max,和记录历史min/max问题。

由于水平有限,目前只有区间取min/max。

如何搞

拿取 min 举例,取 max 同理。

俩同时支持的时候记得处理一下两者之间的关系

考虑现在要对区间 [l,r],每个数和 xmin

设区间最大值为 max(l,r)

如果 xmax(l,r),直接跑路(显然)

否则...继续递归?

这个做法显然是假的。当 x 很小的时候,复杂度就会被卡满。

吉司机的做法是:

维护区间 严格 次大值 max2(l,r)

如果 max2(l,r)<x<max(l,r),那么区间中只有最大值会变。看成减法即可。

如果要求和,就顺便维护一下最大值的个数,算贡献,就可以维护和了

否则我们再继续递归下去。

时间复杂度的证明

设线段树 T 上,“一类点”为:u 的最大值等于父亲的最大值;二类点为 u 的最大值小于父亲的最大值的。

设一颗 T 的势能函数 Φ(T) 为二类点的数量。显然 Φ(T)n

一次区间取 min 的操作更新下来,会有若干个二类点变成一类点 (因为被取 min 了),然后变一个二类点的花费是 O(logn) (一条路径顺下来)

换句话说我们可以用 O(log) 的时间把 Φ 给减一。于是我们的总复杂度不会超过 O(nlogn)

加上更多东西

带上区间加?证明还可靠么?

一次区间加,最多会涉及到 O(logn) 个节点。就算他们全部吧 Φ 加了 1Φ 的最大值也就是 O(nlogn)

然后沿用上一个证明,得到算法的复杂度是 O(nlog2n)

而实际问题中这个 Φ 一般会很小,并且也不是每个节点都能让 Φ+1,所以它实际上跑的和 O(nlogn) 一样快(雾)

带上历史最值?还能做吗?

能,我不会

update 2021.05.04: 现在我会了,见 这里

例题

bzoj 最假女选手

说的就是hjz吧

题面很清楚。维护的东西有点小多。

上面提到的,区间取 min/max 的时候,注意把区间整个取没了

比如我们要和 x,取 min,但是 xmin(l,r),然后整个区间就都是 x

顺便,就算没有这么离谱,在取 min 的时候对 max,max2 的影响也要考虑到

写代码的时候小心一点就行了,挺长的

#include <bits/stdc++.h>
using namespace std;
namespace Flandre_Scarlet
{
    #define N 500005
    #define oo 0x3f3f3f3f
    #define ll long long
    #define F(i,l,r) for(int i=l;i<=r;++i)
    #define D(i,r,l) for(int i=r;i>=l;--i)
    #define Fs(i,l,r,c) for(int i=l;i<=r;c)
    #define Ds(i,r,l,c) for(int i=r;i>=l;c)
    #define MEM(x,a) memset(x,a,sizeof(x))
    #define FK(x) MEM(x,0)
    #define Tra(i,u) for(int i=G.st(u),v=G.to(i);~i;i=G.nx(i),v=G.to(i))
    #define p_b push_back
    #define sz(a) ((int)a.size())
    #define all(a) a.begin(),a.end()
    #define iter(a,p) (a.begin()+p)
    #define PUT(a,n) F(i,1,n) printf("%d ",a[i]); puts("");
    int I() {char c=getchar(); int x=0; int f=1; while(c<'0' or c>'9') f=(c=='-')?-1:1,c=getchar(); while(c>='0' and c<='9') x=(x<<1)+(x<<3)+(c^48),c=getchar(); return ((f==1)?x:-x);}
    template <typename T> void Rd(T& arg){arg=I();}
    template <typename T,typename...Types> void Rd(T& arg,Types&...args){arg=I(); Rd(args...);}
    void RA(int *p,int n) {F(i,1,n) *p=I(),++p;}
    int n,q,a[N];
    void Input()
    {
        n=I();
        F(i,1,n) a[i]=I();
        q=I();
    }
    struct node 
    // 最, 次, 最的数量
    // 最大值和最小值都用这个
    {
        int fir,sec,cnt;
        node() {fir=sec=cnt=0;}
    };
    node merge_max(node a,node b)
    {
        node ans; ans.sec=-oo;
        ans.fir=max(a.fir,b.fir);
        if (ans.fir==a.fir) ans.cnt+=a.cnt;
        if (ans.fir==b.fir) ans.cnt+=b.cnt;
        ans.sec=max(a.sec,b.sec);
        if (a.fir!=b.fir) ans.sec=max(ans.sec,min(a.fir,b.fir));
        return ans;
    }
    node merge_min(node a,node b)
    {
        node ans; ans.sec=oo;
        ans.fir=min(a.fir,b.fir);
        if (ans.fir==a.fir) ans.cnt+=a.cnt;
        if (ans.fir==b.fir) ans.cnt+=b.cnt;
        ans.sec=min(a.sec,b.sec);
        if (a.fir!=b.fir) ans.sec=min(ans.sec,max(a.fir,b.fir));
        return ans;
    }
    // 最大/最小的信息合并
    class DriverSegmentTree
    {
    public:
        node mx[N<<2],mn[N<<2]; ll s[N<<2],ad[N<<2];
        #define ls ix<<1
        #define rs ix<<1|1
        #define lson ls,L,mid
        #define rson rs,mid+1,R
        #define inx  int ix=1,int L=1,int R=n
        #define curr ix,L,R
        inline void up(int ix)
        {
            s[ix]=s[ls]+s[rs];
            mx[ix]=merge_max(mx[ls],mx[rs]);
            mn[ix]=merge_min(mn[ls],mn[rs]);
        }
        inline void addone(ll x,inx) // 单点加
        {
            ad[ix]+=x;
            s[ix]+=x*(R-L+1);
            mx[ix].fir+=x; mx[ix].sec+=x;
            mn[ix].fir+=x; mn[ix].sec+=x;
        }
        inline void cminone(int x,inx) // 在保证 max2<x<max 的情况下, 单点取 min
        {
            s[ix]-=(ll)mx[ix].cnt*(mx[ix].fir-x);
            mx[ix].fir=x; mn[ix].fir=min(mn[ix].fir,x);
            if (mx[ix].fir==mn[ix].fir) // 考虑区间取没的情况
            {
                s[ix]=(ll)mx[ix].fir*(R-L+1);
                mx[ix].cnt=mn[ix].cnt=(R-L+1);
                mx[ix].sec=-oo; mn[ix].sec=oo;
            }
            else
            {
                mn[ix].sec=min(mn[ix].sec,x); // 和对另一边的影响
            }
        }
        inline void cmaxone(int x,inx) // 这边是对称的
        {
            s[ix]+=(ll)mn[ix].cnt*(x-mn[ix].fir);
            mn[ix].fir=x; mx[ix].fir=max(mx[ix].fir,x);
            if (mn[ix].fir==mx[ix].fir)
            {
                s[ix]=(ll)mn[ix].fir*(R-L+1);
                mx[ix].cnt=mn[ix].cnt=(R-L+1);
                mx[ix].sec=-oo; mn[ix].sec=oo;
            }
            else
            {
                mx[ix].sec=max(mx[ix].sec,x);
            }
        }
        inline void down(inx)
        {
            int mid=(L+R)>>1;
            if (ad[ix])
            {
                addone(ad[ix],lson);
                addone(ad[ix],rson);
                ad[ix]=0;
            }
            if (mx[ls].fir>mx[ix].fir and mx[ls].sec<mx[ix].fir) cminone(mx[ix].fir,lson);
            if (mx[rs].fir>mx[ix].fir and mx[rs].sec<mx[ix].fir) cminone(mx[ix].fir,rson);

            if (mn[ls].fir<mn[ix].fir and mn[ls].sec>mn[ix].fir) cmaxone(mn[ix].fir,lson);
            if (mn[rs].fir<mn[ix].fir and mn[rs].sec>mn[ix].fir) cmaxone(mn[ix].fir,rson);
            // 简单讨论一下
        }

        void build(inx)
        {
            ad[ix]=0;
            if (L==R)
            {
                mx[ix].fir=mn[ix].fir=s[ix]=a[L];
                mx[ix].cnt=mn[ix].cnt=1;
                mx[ix].sec=-oo; mn[ix].sec=oo;
                return;
            }
            int mid=(L+R)>>1;
            build(lson); 
            build(rson); 
            up(ix);
        }
        void add(int l,int r,int x,inx)
        {
            if (l<=L and R<=r)
            {
                addone(x,curr);
                return;
            }
            int mid=(L+R)>>1;
            down(curr);
            if (l<=mid) add(l,r,x,lson);
            if (mid<r)  add(l,r,x,rson);
            up(ix);
        }
        void cmin(int l,int r,int x,inx)
        {
            if (x>=mx[ix].fir) return;
            if (l<=L and R<=r and x>mx[ix].sec)
            {
                cminone(x,curr);
                return;
            }
            int mid=(L+R)>>1;
            down(curr);
            if (l<=mid) cmin(l,r,x,lson);
            if (mid<r)  cmin(l,r,x,rson);
            up(ix);
        }
        void cmax(int l,int r,int x,inx)
        {
            if (x<=mn[ix].fir) return;
            if (l<=L and R<=r and x<mn[ix].sec)
            {
                cmaxone(x,curr);
                return;
            }
            int mid=(L+R)>>1;
            down(curr);
            if (l<=mid) cmax(l,r,x,lson);
            if (mid<r)  cmax(l,r,x,rson);
            up(ix);
        }

        ll qsum(int l,int r,inx)
        {
            if (l<=L and R<=r) return s[ix];
            int mid=(L+R)>>1;
            down(curr);
            ll ans=0;
            if (l<=mid) ans+=qsum(l,r,lson);
            if (mid<r)  ans+=qsum(l,r,rson);
            return ans;
        }
        int qmax(int l,int r,inx)
        {
            if (l<=L and R<=r) return mx[ix].fir;
            int mid=(L+R)>>1;
            down(curr);
            int ans=-oo;
            if (l<=mid) ans=max(ans,qmax(l,r,lson));
            if (mid<r)  ans=max(ans,qmax(l,r,rson));
            return ans;
        }
        int qmin(int l,int r,inx)
        {
            if (l<=L and R<=r) return mn[ix].fir;
            int mid=(L+R)>>1;
            down(curr);
            int ans=oo;
            if (l<=mid) ans=min(ans,qmin(l,r,lson));
            if (mid<r)  ans=min(ans,qmin(l,r,rson));
            return ans;
        }
        // 最基本的递归线段树, 没啥好注释的
    }T;
    void Sakuya()
    {
        T.build();
        F(i,1,q)
        {
            int o=I();
            if (o==1)
            {
                int l,r,x; Rd(l,r,x);
                T.add(l,r,x);
            }
            if (o==2)
            {
                int l,r,x; Rd(l,r,x);
                T.cmax(l,r,x);
            }
            if (o==3)
            {
                int l,r,x; Rd(l,r,x);
                T.cmin(l,r,x);
            }
            if (o==4)
            {
                int l,r; Rd(l,r);
                printf("%lld\n",T.qsum(l,r));
            }
            if (o==5)
            {
                int l,r; Rd(l,r);
                printf("%d\n",T.qmax(l,r));
            }
            if (o==6)
            {
                int l,r; Rd(l,r);
                printf("%d\n",T.qmin(l,r));
            }
        }
    }
    void IsMyWife()
    {
        Input();
        Sakuya();
    }
}
#undef int //long long
int main()
{
    Flandre_Scarlet::IsMyWife();
    getchar();
    return 0;
}
posted @   Flandre-Zhu  阅读(679)  评论(0编辑  收藏  举报
编辑推荐:
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
阅读排行:
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】
点击右上角即可分享
微信分享提示