zoukankan      html  css  js  c++  java
  • 浅谈区间最值操作与历史最值问题

    浅谈树状数组与线段树:https://www.cnblogs.com/AKMer/p/9946944.html

    区间最值问题

    Gorgeous Sequence为例:

    对于线段树上每个结点,我们维护最大值,严格次大值,区间和,最大值个数即可。对于修改操作,分为三种情况讨论:

    1、如果当前结点的最大值小于等于(a)的话,直接退出,因为取(max)操作不会对当前区间造成任何影响。

    2、如果当前结点的最大值大于(a)但是次大值小于等于(a),所以最大值都会被改成(a),其它值不受影响。给当前点打上一个区间与(a)(min)的标记和区间然后减去最大值个数乘以最大值与(a)的差值。

    3、如果当前结点的次大值大于(a),那么就暴力递归它的两个子树继续做。

    时间复杂度证明:

    首先第一种和第二种情况都是(O(1))的,我们不用考虑。那么对于第三种情况,怎么证明时间复杂度呢?

    这个时候,吉如一线段树最重要的思想来了。

    不要试着证明每次的复杂度多么多么优秀,总复杂度总会给你一个满意的答案。

    我们记录(fake)为当前线段树的权值,(fake)记录的是所有结点的权值种数和。一开始最多是(nlogn)的。

    对于第三种情况,意味着当前结点的最大值和次大值都将消失不见,被(a)取而代之。也就是说,你递归了多少个结点,(fake)的权值就至少会降低多少。(fake)最少可以降到(2n)(每个结点的权值都只有一种),所以总复杂度是(O(nlogn))的。

    于是乎,吉如一线段树处理这种问题的复杂度就是(O(nlogn))的。

    那么假如加上加标记呢?我们考虑一下,每次区间加最后终止的结点一共会有(logn)个,所以这会使(fake)的权值增加(mlogn),最终复杂度还是可以接受的。

    时间复杂度:(O((n+m)logn))

    空间复杂度:(O(n))

    代码如下:

    #include <cstdio>
    #include <algorithm>
    using namespace std;
    typedef long long ll;
    
    const int maxn=1e6+5;
    
    int n,m;
    int a[maxn];
    
    int read() {
        int x=0,f=1;char ch=getchar();
        for(;ch<'0'||ch>'9';ch=getchar())if(ch=='-')f=-1;
        for(;ch>='0'&&ch<='9';ch=getchar())x=x*10+ch-'0';
        return x*f;
    }
    
    struct segment_tree {
        ll sum[maxn<<2];
        int mx[maxn<<2],se[maxn<<2];
        int tag[maxn<<2],cnt[maxn<<2];
    
        void update(int p) {
            sum[p]=sum[p<<1]+sum[p<<1|1];
            mx[p]=max(mx[p<<1],mx[p<<1|1]);
            cnt[p]=(mx[p]==mx[p<<1])*cnt[p<<1];
            cnt[p]+=(mx[p]==mx[p<<1|1])*cnt[p<<1|1];
            if(mx[p]==mx[p<<1])se[p]=se[p<<1];
            else se[p]=mx[p<<1];
            if(mx[p]==mx[p<<1|1])se[p]=max(se[p],se[p<<1|1]);
            else se[p]=max(se[p],mx[p<<1|1]);
        }
    
        void build(int p,int l,int r) {
            tag[p]=-1;
            if(l==r) {
                sum[p]=mx[p]=a[l];
                se[p]=-1,cnt[p]=1;
                return;
            }
            int mid=(l+r)>>1;
            build(p<<1,l,mid);
            build(p<<1|1,mid+1,r);
            update(p);
        }
    
        void solve(int p,int limit) {
            if(mx[p]<=limit)return;
            if(se[p]<limit&&limit<mx[p]) {
                sum[p]-=1ll*(mx[p]-limit)*cnt[p];
                mx[p]=limit,tag[p]=limit;return;
            }
            solve(p<<1,limit),solve(p<<1|1,limit);
            update(p);
        }
    
        void push_down(int p) {
            if(~tag[p]) {
                solve(p<<1,tag[p]);
                solve(p<<1|1,tag[p]);
                tag[p]=-1;
            }
        }
    
        void Min(int p,int l,int r,int L,int R,int v) {
            if(L<=l&&r<=R) {solve(p,v);return;}
            int mid=(l+r)>>1;push_down(p);
            if(L<=mid)Min(p<<1,l,mid,L,R,v);
            if(R>mid)Min(p<<1|1,mid+1,r,L,R,v);
            update(p);
        }
    
        int queryMx(int p,int l,int r,int L,int R) {
            if(L<=l&&r<=R)return mx[p];
            int mid=(l+r)>>1,res=0;push_down(p);
            if(L<=mid)res=max(res,queryMx(p<<1,l,mid,L,R));
            if(R>mid)res=max(res,queryMx(p<<1|1,mid+1,r,L,R));
            return res;
        }
    
        ll querySum(int p,int l,int r,int L,int R) {
            if(L<=l&&r<=R)return sum[p];
            int mid=(l+r)>>1;ll res=0;
            push_down(p);
            if(L<=mid)res+=querySum(p<<1,l,mid,L,R);
            if(R>mid)res+=querySum(p<<1|1,mid+1,r,L,R);
            return res;
        }
    }T;
    
    int main() {
        int Test=read();
        while(Test--) {
            n=read(),m=read();
            for(int i=1;i<=n;i++)
                a[i]=read();
            T.build(1,1,n);
            for(int i=1;i<=m;i++) {
                int opt=read(),l=read(),r=read();
                if(!opt) {
                    int v=read();
                    T.Min(1,1,n,l,r,v);
                }
                if(opt==1)printf("%d
    ",T.queryMx(1,1,n,l,r));
                if(opt==2)printf("%lld
    ",T.querySum(1,1,n,l,r));
            }
        }
        return 0;
    }
    

    历史最大值

    这个东西不好讲。。。

    一般都是一个数组(a)一个数组(b)(b_i)记录(a_i)曾经到达过的最大/最小值或者是每次操作完之后(a_i)的和。然后在(a)上操作来操作去,冷不丁的问你(b)数组的区间最大/最小值或者是区间和。

    这种问题一般分为四类(第二类和第四类如果我碰见了果断暴力分走起)

    第一类:可以用延迟标记处理的问题

    这类问题一般用延迟标记可以解决,不过要记录标记从上一次下传标记到现在这一次的最值。维护的东西比较多,但是并不会涉及什么骚操作,对合并标记功法要求很高。

    第二类:无法用延迟标记处理的问题

    都说了不能用延迟标记处理了就弃了吧~根本不会

    第三类:无区间最值操作的区间问题

    一般都要用到辅助数组,转化成传统的问题。

    第四类:有区间最值操作的区间问题

    无区间最值操作都不会了,有区间最值还玩屁~根本不会

    参考资料:国家集训队2016论文集——吉如一《区间最值操作与历史最值问题》

    竞赛是灵活的,所以我觉得吉司机线段树要考也不会变到哪去,只要掌握这种以全局来分析问题复杂度的思想,就没问题了。这就是我不学历史最值问题的理由

  • 相关阅读:
    java集合 (hashmap,hashtable,list,set)
    struts2+hibernate+spring 事物控制
    eclipse 添加xml文件提示功能
    myeclipse 快捷键 收藏
    设置ORACLE客户端字符集
    批处理删除文件夹
    java作用域public,private,protected ,default区别
    CCV所用资料资源汇总
    总序
    数字图像处理第一次作业
  • 原文地址:https://www.cnblogs.com/AKMer/p/10225100.html
Copyright © 2011-2022 走看看