數列分塊求區間衆數(學習筆記)

數列分塊

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;
}
發佈了45 篇原創文章 · 獲贊 27 · 訪問量 1萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章