数列分块求区间众数(学习笔记)

数列分块

1. 区间加法,区间求和

简单分析一下:将数列划分为n个块,对于区间修改,整块的进行atag标记一下,非整块的最多只有2n个,暴力加一下
复杂度:O(n)


#include<iostream>
#include<cstdio>
#include<cmath>
#include<cstring>
#define ll long long
#define mem(a,b) memset(a,b,sizeof a)
using namespace std;

ll read()
{
    ll x=0,f=1;char ch=getchar();
    while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
    while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
    return x*f;
}

const int maxn=50005;
int n,block;
int v[maxn],bl[maxn],atag[maxn];

void add(int a,int b,int c)
{
    for(int i=a;i<=min(b,block*bl[a]);i++)
    {
        v[i]+=c;
    }
    if(bl[a]!=bl[b])
    {
        for(int i=block*(bl[b]-1)+1;i<=b;i++)
        {
            v[i]+=c;
        }
    }
    for(int i=bl[a]+1;i<=bl[b]-1;i++)
    {
        atag[i]+=c;
    }
}

int main()
{

    int f,a,b,c;
    n=read();block=sqrt(n);
    mem(atag,0);
    for(int i=1;i<=n;i++)v[i]=read();
    for(int i=1;i<=n;i++)bl[i]=(i-1)/block+1;
    for(int i=1;i<=n;i++)
    {
        f=read();
        if(f==0)
        {
            a=read();b=read();c=read();
            add(a,b,c);
        }
        if(f==1)
        {
            a=read();
            printf("%d\n",v[a]+atag[bl[a]]);
        }
    }
    return 0;
}

2. 区间加法,询问小于某个数的个数。


#include<iostream>
#include<cstdio>
#include<cmath>
#include<vector>
#include<cstring>
#include<algorithm>
#define ll long long
#define mem(a,b) memset(a,b,sizeof a)
using namespace std;

ll read()
{
    ll x=0,f=1;char ch=getchar();
    while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
    while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
    return x*f;
}

const int maxn=50005;
int n,block;
int v[maxn],bl[maxn],atag[maxn];
vector<int >ve[maxn];
void add(int a,int b,int c)
{
    for(int i=a;i<=min(b,block*bl[a]);i++)
    {
        v[i]+=c;
    }
    if(bl[a]!=bl[b])
    {
        for(int i=bl[b]*(block-1)+1;i<=b;i++)
        {
            v[i]+=c;
        }
    }
    for(int i=bl[a]+1;i<=bl[b]-1;i++)
    {
        atag[i]+=c;
    }
}
int query(int a,int b,int x)
{
    int ans=0;
    for(int i=1;i<=min(bl[a]*block,b);i++)
    {
        if(v[i]+atag[bl[a]]<x)
        {
            ans++;
        }
    }
    if(bl[a]!=bl[b])
    for(int i=block*(bl[b]-1)+1;i<=b;i++)
    {
        if(v[i]+atag[bl[b]]<x)ans++;
    }
    for(int i=bl[a]+1;i<=bl[b]-1;i++)
    {
        x-=atag[i];
        ans+=lower_bound(ve[i].begin(),ve[i].end(),x)-ve[i].begin();
    }
    return ans;
}
int main()
{

    int f,a,b,c;
    n=read();block=sqrt(n);
    mem(atag,0);
    for(int i=1;i<=n;i++)
    {
        v[i]=read();
        bl[i]=(i-1)/block+1;
    }
    for(int i=1;i<=n;i++)
    {
        ve[bl[i]].push_back(v[i]);
    }
    for(int i=1;i<=bl[n];i++)
    {
        sort(ve[i].begin(),ve[i].end());
    }
    for(int i=1;i<=n;i++)
    {
        f=read();
        if(f==0)
        {
            a=read();b=read();c=read();
            add(a,b,c);
        }
        if(f==1)
        {
            a=read();b=read();c=read();
            printf("%d\n",query(a,b,c));
        }
    }
    return 0;
}
/*
9
1 3 2 5 7 9 4 6 8
0 1 4 1
1 1 5 4
*/

3. 区间加法,询问区间内小于某个值x的前驱(小于x的最大值)

做法和2一样,改变一下,query即可


#include<iostream>
#include<cstdio>
#include<cmath>
#include<vector>
#include<cstring>
#include<algorithm>
#define ll long long
#define mem(a,b) memset(a,b,sizeof a)
using namespace std;

ll read()
{
    ll x=0,f=1;char ch=getchar();
    while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
    while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
    return x*f;
}

const int maxn=50005;
int n,block;
int v[maxn],bl[maxn],atag[maxn];
vector<int >ve[maxn];
void add(int a,int b,int c)
{
    for(int i=a;i<=min(b,block*bl[a]);i++)
    {
        v[i]+=c;
    }
    if(bl[a]!=bl[b])
    {
        for(int i=bl[b]*(block-1)+1;i<=b;i++)
        {
            v[i]+=c;
        }
    }
    for(int i=bl[a]+1;i<=bl[b]-1;i++)
    {
        atag[i]+=c;
    }
}
int query(int a,int b,int x)
{
    int ans=0;
    for(int i=1;i<=min(bl[a]*block,b);i++)
    {
        if(v[i]+atag[bl[a]]<x)
        {
            ans=max(ans,v[i]);
        }
    }
    if(bl[a]!=bl[b])
    for(int i=block*(bl[b]-1)+1;i<=b;i++)
    {
        if(v[i]+atag[bl[b]]<x)ans=max(ans,v[i]);
    }
    for(int i=bl[a]+1;i<=bl[b]-1;i++)
    {
        x-=atag[i];
        int k=lower_bound(ve[i].begin(),ve[i].end(),x)-ve[i].begin();
        ans=max(ans,v[k-1]);
    }
    return ans;
}
int main()
{

    int f,a,b,c;
    n=read();block=sqrt(n);
    mem(atag,0);
    for(int i=1;i<=n;i++)
    {
        v[i]=read();
        bl[i]=(i-1)/block+1;
    }
    for(int i=1;i<=n;i++)
    {
        ve[bl[i]].push_back(v[i]);
    }
    for(int i=1;i<=bl[n];i++)
    {
        sort(ve[i].begin(),ve[i].end());
    }
    for(int i=1;i<=n;i++)
    {
        f=read();
        if(f==0)
        {
            a=read();b=read();c=read();
            add(a,b,c);
        }
        if(f==1)
        {
            a=read();b=read();c=read();
            printf("%d\n",query(a,b,c));
        }
    }
    return 0;
}
/*
9
1 3 2 5 7 9 4 6 8
0 1 4 1
1 1 5 4
*/

4.区间加法,区间区和


#include<iostream>
#include<cstdio>
#include<cmath>
#include<cstring>
#define ll long long
#define mem(a,b) memset(a,b,sizeof a)
using namespace std;

ll read()
{
    ll x=0,f=1;char ch=getchar();
    while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
    while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
    return x*f;
}

const int maxn=50005;
int n,block;
int v[maxn],bl[maxn],atag[maxn],sum[maxn];

void add(int a,int b,int c)
{
    for(int i=a;i<=min(b,block*bl[a]);i++)
    {
        v[i]+=c;sum[bl[a]]+=c;
    }
    if(bl[a]!=bl[b])
    {
        for(int i=block*(bl[b]-1)+1;i<=b;i++)
        {
            v[i]+=c;sum[bl[b]]+=c;
        }
    }
    for(int i=bl[a]+1;i<=bl[b]-1;i++)
    {
        atag[i]+=c;
    }
}
int query(int a,int b)
{
    int ans=0;
    for(int i=a;i<=min(block*bl[a],b);i++)
    {
        ans+=v[i]+atag[bl[a]];
    }
    if(bl[a]!=bl[b])
    {
        for(int i=block*(bl[b]-1)+1;i<=b;i++)
        {
            ans+=v[i]+atag[bl[b]];
        }
    }
    for(int i=bl[a]+1;i<=bl[b]-1;i++)
    {
        ans+=sum[i]+atag[i]*block;
    }
    return ans;
}
int main()
{

    int f,a,b,c;
    n=read();block=sqrt(n);
    mem(atag,0);
    for(int i=1;i<=n;i++)v[i]=read();
    for(int i=1;i<=n;i++)
    {
        bl[i]=(i-1)/block+1;
        sum[bl[i]]+=v[i];
    }
    for(int i=1;i<=n;i++)
    {
        f=read();
        if(f==0)
        {
            a=read();b=read();c=read();
            add(a,b,c);
        }
        if(f==1)
        {
            a=read(),b=read();
            printf("%d\n",query(a,b));
        }
    }
    return 0;
}

5.区间开方求和


#include<iostream>
#include<cstdio>
#include<cmath>
#include<cstring>
#define ll long long
#define mem(a,b) memset(a,b,sizeof a)
using namespace std;

ll read()
{
    ll x=0,f=1;char ch=getchar();
    while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
    while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
    return x*f;
}

const int maxn=50005;
int n,block;
int v[maxn],bl[maxn],atag[maxn],sum[maxn],flag[maxn];

void solve_sqrt(int x)
{
    if(flag[x])return ;
    flag[x]=1;
    sum[x]=0;
    for(int i=(x-1)*block+1;i<=x*block;i++)
    {
        v[i]=sqrt(v[i]);
        sum[x]+=v[i];
        if(v[i]>1)flag[x]=0;
    }
}

void add(int a,int b)
{
    for(int i=a;i<=min(b,block*bl[a]);i++)
    {
        sum[bl[a]]-=v[i];
        v[i]=sqrt(v[i]);
        sum[bl[a]]+=v[i];
    }
    if(bl[a]!=bl[b])
    {
        for(int i=block*(bl[b]-1)+1;i<=b;i++)
        {
            sum[bl[b]]-=v[i];
            v[i]=sqrt(v[i]);
            sum[bl[b]]+=v[i];
        }
    }
    for(int i=bl[a]+1;i<=bl[b]-1;i++)
    {
        solve_sqrt(i);
    }
}
int query(int a,int b)
{
    int ans=0;
    for(int i=a;i<=min(block*bl[a],b);i++)
    {
        ans+=v[i];
    }
    if(bl[a]!=bl[b])
    {
        for(int i=block*(bl[b]-1)+1;i<=b;i++)
        {
            ans+=v[i];
        }
    }
    for(int i=bl[a]+1;i<=bl[b]-1;i++)
    {
        ans+=sum[i];
    }
    return ans;
}
int main()
{

    int f,a,b,c;
    n=read();block=sqrt(n);
    mem(atag,0);
    for(int i=1;i<=n;i++)v[i]=read();
    for(int i=1;i<=n;i++)
    {
        bl[i]=(i-1)/block+1;
        sum[bl[i]]+=v[i];
    }
    for(int i=1;i<=n;i++)
    {
        f=read();
        if(f==0)
        {
            a=read();b=read();
            add(a,b);
        }
        if(f==1)
        {
            a=read(),b=read();
            printf("%d\n",query(a,b));
        }
    }
    return 0;
}

6.单点插入,单点询问

注意:这可能导致某个块数量剧增,因此引入重构
重构:1)每n次插入后,重新把数列平均分一下块,重构需要的复杂度为O(n),重构的次数为n,所以重构的复杂度没有问题,而且保证了每个块的大小相对均衡。
2)当然,也可以当某个块过大时重构,或者只把这个块分成两半。

#include<iostream>
#include<cstdio>
#include<cmath>
#include<vector>
#include<cstring>
#define ll long long
#define mem(a,b) memset(a,b,sizeof a)
using namespace std;

ll read()
{
    ll x=0,f=1;char ch=getchar();
    while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
    while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
    return x*f;
}

const int maxn=50005;
int n,block;
int v[maxn],bl[maxn],atag[maxn];
int st[2*maxn],top,m;
vector<int> ve[maxn];


void rebuild()
{
    top=0;
    for(int i=1;i<=m;i++)
    {
        for(vector<int>::iterator j=ve[i].begin();j!=ve[i].end();j++)
        {
            st[++top]=*j;
        }
        ve[i].clear();
    }
    int blo2=sqrt(top);
    for(int i=1;i<=top;i++)
    {
        ve[(i-1)/blo2+1].push_back(st[i]);
    }
    m=(top-1)/blo2+1;
}

pair<int ,int> query(int x)
{
    int a=1;
    while(x>ve[a].size())
    {
        x-=ve[a].size();
        a++;
    }
    return make_pair(a,x-1);
}
void insert(int a,int b)
{
    pair<int ,int> t=query(a);
    ve[t.first].insert(ve[t.first].begin()+t.second,b);
    if(ve[t.first].size()>20*block)rebuild();
}


int main()
{

    int f,a,b,c;
    n=read();block=sqrt(n);
    mem(atag,0);
    for(int i=1;i<=n;i++)v[i]=read();
    for(int i=1;i<=n;i++)
    {
        bl[i]=(i-1)/block+1;
        ve[bl[i]].push_back(v[i]);
    }
    for(int i=1;i<=n;i++)
    {
        f=read();
        if(f==0)
        {
            a=read();b=read();
            insert(a,b);
        }
        if(f==1)
        {
            a=read();
            pair<int,int> t=query(a);
            printf("%d\n",ve[t.first][t.second]);
        }
    }
    return 0;
}

7.区间加法,区间乘法,单点查值

#include<iostream>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<cstring>
#define ll long long
using namespace std;
const int maxn=50005;

int v[maxn],atag[maxn],mtag[maxn],bl[maxn],block,n;

ll read()
{
    int f=1,x=0;
    char c;c=getchar();
    while(c<'0'||c>'9'){if(c=='-')f=-1;c=getchar();}
    while(c<='9'&&c>='0'){x=x*10+c-'0';c=getchar();}
    return x*f;
}
void reset(int x)
{
    for(int i=(x-1)*block+1;i<=min(x*block,n);i++)//注意终点
    {
        v[i]=v[i]*mtag[x]+atag[x];
    }
    mtag[x]=1;atag[x]=0;
}
void add(int f,int a,int b,int c)
{
    reset(bl[a]);
    for(int i=a;i<=min(bl[a]*block,b);i++)
    {
        if(f==0)//加
        v[i]+=c;
        else v[i]*=c;
    }
    if(bl[a]!=bl[b])
    {
        reset(bl[b]);
        for(int i=(bl[b]-1)*block+1;i<=b;i++)
        {
            if(f==0)v[i]+=c;
            else v[i]*=c;
        }
    }
    for(int i=bl[a]+1;i<=bl[b]-1;i++)
    {
        if(f==0)
        {
            atag[i]+=c;
        }
        else
        {
            atag[i]=c*atag[i];
            mtag[i]=mtag[i]*c;
        }
    }
}
int main()
{
    int f,a,b,c;
    n=read();block=sqrt(n);
    for(int i=1;i<=n;i++)mtag[i]=1;
    for(int i=1;i<=n;i++)
    {
        v[i]=read();
        bl[i]=(i-1)/block+1;
    }
    for(int i=1;i<=n;i++)
    {
        f=read();
        if(f==2)//单点查询
        {
            a=read();
            printf("%d\n",v[a]*mtag[bl[a]]+atag[bl[a]]);
        }
        else{
            a=read();b=read();c=read();
            add(f,a,b,c);
        }
    }
    return 0;
}

8.区间查询c的个数,并将区间所有值更新为c

#include<iostream>
#include<cmath>
#include<algorithm>
#include<cstdio>
#include<cstring>
#define ll long long
using namespace std;

const int maxn=50005;

int v[maxn],bl[maxn],tag[maxn],n,block;

ll read()
{
    int f=1,x=0;
    char c=getchar();
    while(c<'0'||c>'9'){if(c=='-')f=-1;c=getchar();}
    while(c>='0'&&c<='9'){x=x*10+c-'0';c=getchar();}
    return f*x;
}

void reset(int x)
{
    if(tag[x]==-1)return;
    for(int i=(x-1)*block+1;i<=min(block*x,n);i++)
    {
        v[i]=tag[x];
    }
    tag[x]=-1;
}

int query(int a,int b,int c)
{
    int ans=0;
    reset(bl[a]);
    for(int i=a;i<=min(b,block*bl[a]);i++)
    {
        if(v[i]==c)ans++;
        else v[i]=c;
    }
    if(bl[a]!=bl[b])
    {
        reset(bl[b]);
        for(int i=(bl[b]-1)*block+1;i<=b;i++)
        {
            if(v[i]==c)ans++;
            else v[i]=c;
        }

    }
    for(int i=bl[a]+1;i<=bl[b]-1;i++)
    {
        if(tag[i]!=-1)
        {
            if(tag[i]!=c)tag[i]=c;
            else ans+=block;
        }
        else{
            for(int j=(i-1)*block+1;j<=i*block;j++)
            {
                if(v[j]==c)ans++;
                else v[j]=c;
            }
            tag[i]=c;
        }
    }
    return ans;
}
int main()
{
    int f,a,b,c;
    n=read();block=sqrt(n);
    for(int i=1;i<=n;i++)
    {
        v[i]=read();
        bl[i]=(i-1)/block+1;
    }
    memset(tag,-1,sizeof tag);
    for(int i=1;i<=n;i++)
    {
        a=read();b=read();c=read();
        printf("%d\n",query(a,b,c));
    }
    return 0;
}
/*
9
1 3 2 5 7 9 4 6 8
*/

9.求区间众数

这是一道相对较难的题处理过程:首先处理要查询的区间连续整块区间的的众数ans,然后处理非整块的2*block个数。
然后思考两个问题:1).连续整块区间的众数会是整个区间的众数吗?(显然能取出反例)
2).如果连续整块区间的众数不能作为整块区间的众数,那么我们处理它有什么用?
回答:如果ans不是众数,那么必然存在另一个数是众数,并且这个众数必然在另 外的2*block个数中出现过,那个枚举可能成为众数的2*block个数与ans比较即可。
具体见代码:

#include<iostream>
#include<cstdio>
#include<cstring>
#include<map>
#include<algorithm>
#include<vector>
#define ll long long
using namespace std;

const int maxn=50005;
int n,block,v[maxn],bl[maxn],id  ;
int f[505][505];//记录从块i到块j的众数的val的下标(也是v[i])
map<int,int >mp;
int val[maxn],cnt[maxn];
vector<int>ve[maxn];

ll read()
{
    int f=1,x=0;
    char c;c=getchar();
    while(c<'0'||c>'9'){if(c=='-')f=-1;c=getchar();}
    while(c<='9'&&c>='0'){x=x*10+c-'0';c=getchar();}
    return x*f;
}
void pre(int x)
{
    memset(cnt,0,sizeof cnt);
    int t=0,mx=0;
    for(int i=(x-1)*block+1;i<=n;i++)
    {
        cnt[v[i]]++;
        if((cnt[v[i]]>mx)||(cnt[v[i]]==mx&&val[v[i]]<val[t]))
        {
            t=v[i];
            mx=cnt[v[i]];
        }
        f[x][bl[i]]=t;
    }
}
int query(int l,int r,int x)
{
    return upper_bound(ve[x].begin(),ve[x].end(),r)-lower_bound(ve[x].begin(),ve[x].end(),l);
}
int query(int a,int b)
{
    int ans=f[bl[a]+1][bl[b]-1];
    int mx=query(a,b,ans);
    int t;
    for(int i=a;i<=min(bl[a]*block,b);i++)
    {
        t=query(a,b,v[i]);
        if((t>mx)||(t==mx&&val[v[i]]<val[ans]))
        {
            ans=v[i];
            mx=t;
        }
    }
    if(bl[a]!=bl[b])
    {
        for(int i=(bl[b]-1)*block+1;i<=b;i++)
        {
            t=query(a,b,v[i]);
            if((t>mx)||(t==mx&&val[v[i]]<val[ans]))
            {
                ans=v[i];
                mx=t;
            }
        }
    }
    return ans;
}
int main()
{
    int a,b;
    n=read();block=200;id=0;
    for(int i=1;i<=n;i++)
    {
        v[i]=read();
        if(!mp[v[i]])
        {
            mp[v[i]]=++id;
            val[id]=v[i];
        }
        v[i]=mp[v[i]];
        ve[v[i]].push_back(i);
    }
    for(int i=1;i<=n;i++)
    {
        bl[i]=(i-1)/block+1;
    }
    for(int i=1;i<=bl[n];i++)pre(i);
    for(int i=1;i<n;i++)
    {
        a=read();b=read();
        if(a>b)swap(a,b);
        printf("%d\n",val[query(a,b)]);
    }
    return 0;
}
阅读更多
个人分类: 数列分块
想对作者说点什么? 我来说一句

没有更多推荐了,返回首页

关闭
关闭
关闭