笔记 - 吉司机线段树

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

吉司机线段树

简介

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

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

如何搞

拿取 min 举例,取 max 同理。

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

考虑现在要对区间 \([l,r]\),每个数和 \(x\)\(min\)

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

如果 \(x\ge \max(l,r)\),直接跑路(显然)

否则...继续递归?

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

吉司机的做法是:

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

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

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

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

时间复杂度的证明

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

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

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

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

加上更多东西

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

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

然后沿用上一个证明,得到算法的复杂度是 \(O(n\log^2 n)\)

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

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

能,我不会

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

例题

bzoj 最假女选手

说的就是hjz吧

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

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

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

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

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

#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 @ 2021-02-06 23:04  Flandre-Zhu  阅读(590)  评论(0编辑  收藏  举报