NOIP模板复习(4)区间操作之莫队算法,树状数组,线段树

NOIP模板复习(4) 区间操作之莫队算法,树状数组,线段树

目录

1.莫队算法

1.1算法原理

1.2算法实现

2.树状数组

2.1结构原理

2.2查询操作

2.3修改操作

3.线段树

3.1结构原理

3.2单点修改+查询

3.3Lazy标记

3.4Lazy实现


1.莫队算法

  莫队算法,顾名思义,是由前国家队队长莫涛神犇发明的区间离线查询算法。时间复杂度为(O(nsqrt{n})),可以维护一些线段树不好维护或干脆没法维护信息。


1.1算法原理

  莫队算法被称为"优雅的暴力",其实就是利用询问的区间间的关系来做到降低指针无效移动次数的算法。具体是用分块和排序来实现对查询操作的维护。

  具体来讲就是先将原数列进行每块操作,再按查询的左端点所在的块和右端点排序。


1.2算法实现

  这里以询问区间内某一数字数量为例。


#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <cmath>
#include <algorithm>
using namespace std;
int block[10005];
int ans[10005];
int num[10005];
struct query
{
    int l,r,num,id;
    bool operator <(const query& mid)const
    {
        if(block[this->l]==block[mid.l])
        {
            return this->r<mid.r;
        }
        return this->l<mid.l;
    }
};
query ask[10005];
int cnt[10005];
int main()
{
    int n,m;
    scanf("%d %d",&n,&m);
    int size=sqrt(n);
    for(int i=1;i<=n;i++)
    {
        scanf("%d",&num[i]);
        block[i]=i/size;
    }
    for(int i=1;i<=m;i++)
    {   
        scanf("%d %d %d",&ask[i].l,&ask[i].r,&ask[i].num);
        ask[i].id=i;
    }
    sort(ask+1,ask+1+m);
    int l=1;
    int r=0;
    for(int i=1;i<=m;i++)
    {
        while(l<ask[i].l)
        {
            cnt[num[l]]--;
            l++;
        }
        while(r<ask[i].r)
        {
            r++;
            cnt[num[r]]++;
        }
        while(l>ask[i].l)
        {
            l--;
            cnt[num[l]]++;
        }
        while(r>ask[i].r)
        {
            cnt[num[r]]--;
            r--;
        }
        ans[ask[i].id]=cnt[ask[i].num];
    }
    for(int i=1;i<=m;i++)
    {
        cout<<ans[i]<<endl;
    }
    return 0;
}


2.树状数组

   树状数组是一种利用二进制原理维护区间信息的数据结构,可以在(O(nlog_2(n)))的时间内查询和修改,虽然其修改和查询的范围有限,但因为其实现简单和与线段树相比的较低的时间常数,让它在某些范围内经常使用。


2.1结构原理

  因为树状数组的结构原理较难理解,本人就不在这里献丑了(其实是懒),推荐阅读以下博客:传送门


2.2结构实现

  这里以区间加法和区间和为例。


#include <iostream>
#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <algorithm>
#include <cmath>
using namespace std;
int num[10005];
int tree[10005];
char str[5];
int n,m;
inline int lowbit(int s)
{
    return s&(-s);
}
void add(int pos,int nums)
{
    for(int i=pos;i<=n;i+=lowbit(i))
    {
        tree[i]+=nums;
    }
    return ;
}
int query(int pos)
{
    int ans=0;
    for(int i=pos;i>0;i-=lowbit(i))
    {
        ans+=tree[i];
    }
    return ans;
}
int main()
{
    scanf("%d %d",&n,&m);
    for(int i=1;i<=n;i++)
    {
        scanf("%d",&num[i]);
        add(i,num[i]);
    }
    for(int i=1;i<=m;i++)
    {
        scanf("%s",&str[1]);
        if(str[1]=='A')
        {
            int pos,addval;
            scanf("%d %d",&pos,&addval);
            add(pos,addval);
        }
        else if(str[1]=='Q')
        {
            int l,r;
            scanf("%d %d",&l,&r);
            cout<<query(r)-query(l-1)<<endl;
        }
    }
    return 0;
}


3.线段树

  线段树,从名字就可以看出这是一种树形结构。其中每个节点表示一个区间的信息,同时线段是也是一个完全二叉树,通过二分的思想可以实现总共(为O(nlog_2n))的时间复杂度,其中单个查询和修改的时间复杂度仅为(O(log_2n))


3.1结构原理

  通过建立一个完全二叉树,将每个区间的信息进行递归式合并。通过将每个区间拆分为两段来实现高效的查询。同时,因为是一颗完全二叉树,所以可以在一个线性的数组中存放。


3.2单点修改+区间查询

  以单点加法和区间查询最大值为例。


#include <iostream>
#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <cmath>
#include <algorithm>
using namespace std;
int tree[10005<<2];
int num[10005];
char str[5];
void build(int root,int l,int r)
{
    if(l==r)
    {
        tree[root]=num[l];
        return ;
    }
    int mid=(l+r)>>1;
    build(root<<1,l,mid);
    build(root<<1|1,mid+1,r);
    tree[root]=max(tree[root<<1],tree[root<<1|1]);
    return ;
}
void updata(int root,int l,int r,int pos,int val)
{
    if(l==r)
    {
        if(l==pos)
        {
            tree[root]+=val;
        }
        return ;
    }
    int mid=(l+r)>>1;
    if(pos<=mid)
    {
        updata(root<<1,l,mid,pos,val);
    }
    else
    {
        updata(root<<1|1,mid+1,r,pos,val);
    }
    tree[root]=max(tree[root<<1],tree[root<<1|1]);
}
int query(int root,int l,int r,int ql,int qr)
{
    if(ql>r||qr<l)
    {
        return 0;
    }
    if(ql<=l&&qr>=r)
    {
        return tree[root];
    }
    int mid=(l+r)>>1;
    int lson=query(root<<1,l,mid,ql,qr);
    int rson=query(root<<1|1,mid+1,r,ql,qr);
    return max(lson,rson);
}
int main()
{
    int n,m;
    scanf("%d %d",&n,&m);
    for(int i=1;i<=n;i++)
    {
        scanf("%d",&num[i]);
    }
    build(1,1,n);
    for(int i=1;i<=m;i++)
    {
        scanf("%s",&str[1]);
        if(str[1]=='A')
        {
            int pos,val;
            scanf("%d %d",&pos,&val);
            updata(1,1,n,pos,val);
        }
        else if(str[1]=='Q')
        {
            int l,r;
            scanf("%d %d",&l,&r);
            cout<<query(1,1,n,l,r)<<endl;
        }
    }
    return 0;
}


3.3Lazy标记

  因为单个节点的修改复杂度为(O(log_2n))当遇到区间修改的时候,复杂度就会变得无法接受,因此引入了Lazy标记这个东西,当我们要进行修改的时候可以先将修改的信息记在Lazy标记中,直到查询时才将需要的区间的信息进行下传。这样可以在一定程度上降低无用操作的时间复杂度。


3.4Lazy标记实现

  以区间加法和区间最大值为例


#include <iostream>
#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <cmath>
#include <algorithm>
using namespace std;
struct node
{
    int val,lazytag;
};
node tree[10005<<2];
int num[10005];
void pushdown(int root)
{
    if(tree[root].lazytag)
    {
        tree[root<<1].lazytag+=tree[root].lazytag;
        tree[root<<1|1].lazytag+=tree[root].lazytag;
        tree[root<<1].val+=tree[root].lazytag;
        tree[root<<1|1].val+=tree[root].lazytag;
        tree[root].lazytag=0;
    }
    return ;
}
void build(int root,int l,int r)
{
    tree[root].lazytag=0;
    if(l==r)
    {
        tree[root].val=num[l];
        return ;
    }
    int mid=(l+r)>>1;
    build(root<<1,l,mid);
    build(root<<1|1,mid+1,r);
    tree[root].val=max(tree[root<<1].val,tree[root<<1|1].val);
    return ;
}
void updata(int root,int l,int r,int ql,int qr,int val)
{
    if(ql>r||qr<l)
    {
        return ;
    }
    if(ql>=l&&qr<=r)
    {
        tree[root].val+=val;
        tree[root].lazytag+=val;
        return ;
    }
    pushdown(root);
    int mid=(l+r)>>1;
    updata(root<<1,l,mid,ql,qr,val);
    updata(root<<1|1,mid+1,r,ql,qr,val);
    tree[root].val=max(tree[root<<1].val,tree[root<<1|1].val);
    return ;
}
int query(int root,int l,int r,int ql,int qr)
{
    if(ql>r||qr<l)
    {
        return 0;
    }
    if(ql>=l&&qr<=r)
    {
        return tree[root].val;
    }
    pushdown(root);
    int mid=(l+r)>>1;
    int lson=query(root<<1,l,mid,ql,qr);
    int rson=query(root<<1|1,mid+1,r,ql,qr);
    return tree[root].val=max(tree[root<<1].val,tree[root<<1|1].val);
}
int main()
{
    int n,m;
    for(int i=1;i<=n;i++)
    {
        scanf("%d",&num[i]);
    }
    build(1,1,n);
    for(int i=1;i<=m;i++)
    {
        scanf("%s",&str[1]);
        if(str[1]=='A')
        {
            int l,r,val;
            scanf("%d %d %d",&l,&r,&val);
            updata(1,1,n,l,r,val);
        }
        else if(str[1]=='Q')
        {
            int l,r;
            scanf("%d %d",&l,&r);
            cout<<query(1,1,n,l,r)<<endl;
        }
    }
    return 0;
}

原文地址:https://www.cnblogs.com/Heizesi/p/7787963.html