数据结构与算法:分块(一):块状数组、块状链表

前言

这玩意儿也太妙了!!

一、内容

对于 n 长度的数组,考虑将其平均划分为 块,那么就有块数和每个块内的长度都是 的级别,所以此时就能实现遍历所有块和块内遍历一遍的复杂度都是 的了。

在维护信息时,块内就可以暴力维护,整块直接修改标签即可。那么在处理时,就只需要对左右散块暴力,中间块遍历即可。

二、适用场景

当区间信息不可合并或者很难合并时,那么就无法使用线段树了。而此时往往分块比较容易解决,因为块内信息是暴力维护的。除此之外,分块在思维上比其他正解是要好想的,在想不到正解的时候可以考虑分块暴力草()

三、题目

1.Give Away

cpp 复制代码
#include<bits/stdc++.h>
using namespace std;

/*   /\_/\
*   (= ._.)
*   / >  \>
*/

/*
*想好再写
*注意审题 注意特判
*不要红温 不要急躁 耐心一点
*WA了不要立马觉得是思路不对 先耐心找反例
*/

#define endl '\n'
#define dbg(x) cout<<#x<<endl;cout<<x<<endl;
#define vdbg(a) cout<<#a<<endl;for(auto x:a)cout<<x<<" ";cout<<endl;
#define YES cout<<"YES"<<endl;return ;
#define Yes cout<<"Yes"<<endl;return ;
#define NO cout<<"NO"<<endl;return ;
#define No cout<<"No"<<endl;return ;
typedef long long ll;
typedef long double ld;
typedef pair<int,int> pii;
typedef pair<ll,ll> pll;
const int INF=1e9;
const ll INFLL=1e18;
const int dx[]={-1,1,0,0};
const int dy[]={0,0,-1,1};
const int ddx[]={-2,-1,1,2,2,1,-1,-2};
const int ddy[]={1,2,2,1,-1,-2,-2,-1};

void solve()
{
    int n;
    cin>>n;
    vector<int>a(n+1);
    for(int i=1;i<=n;i++)
    {
        cin>>a[i];
    }

    int blen=sqrt(n);
    int bnum=(n+blen-1)/blen;
    vector<int>bi(n+1);
    for(int i=1;i<=n;i++)
    {
        bi[i]=(i-1)/blen+1;
    }

    vector<int>bl(blen+1);
    vector<int>br(blen+1);
    for(int i=1;i<=bnum;i++)
    {
        bl[i]=(i-1)*blen+1;
        br[i]=min(i*blen,n);
    }

    vector<int>sorted(n+1);
    for(int i=1;i<=n;i++)
    {
        sorted[i]=a[i];
    }
    for(int i=1;i<=bnum;i++)
    {
        sort(sorted.begin()+bl[i],sorted.begin()+br[i]+1);
    }

    auto bs=[&](int i,int v)->int
    {
        int l=bl[i];
        int r=br[i];
        int m;
        int ans=r+1;
        while(l<=r)
        {
            m=l+r>>1;
            if(sorted[m]>=v)
            {
                ans=m;
                r=m-1;
            }
            else
            {
                l=m+1;
            }
        }
        return br[i]-ans+1;
    };

    auto query=[&](int l,int r,int v)->int
    {
        if(bi[l]==bi[r])
        {
            int ans=0;
            for(int i=l;i<=r;i++)
            {
                if(a[i]>=v)
                {
                    ans++;
                }
            }
            return ans;
        }
        
        int ans=0;
        for(int i=l;i<=br[bi[l]];i++)
        {
            if(a[i]>=v)
            {
                ans++;
            }
        }
        for(int i=bi[l]+1;i<=bi[r]-1;i++)
        {
            ans+=bs(i,v);
        }
        for(int i=bl[bi[r]];i<=r;i++)
        {
            if(a[i]>=v)
            {
                ans++;
            }
        }
        return ans;
    };  

    auto update=[&](int p,int v)->void
    {
        int l=bl[bi[p]];
        int r=br[bi[p]];
        a[p]=v;
        for(int i=l;i<=r;i++)
        {
            sorted[i]=a[i];
        }
        sort(sorted.begin()+l,sorted.begin()+r+1);
    };

    int m;
    cin>>m;
    int op,i,x,l,r,v;
    while(m--)
    {
        cin>>op;
        if(op==0)
        {
            cin>>l>>r>>v;
            cout<<query(l,r,v)<<endl;
        }
        else
        {
            cin>>i>>x;
            update(i,x);
        }
    }
}

void init()
{
}

signed main()
{
    ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
    int t=1;
    //cin>>t;
    init();
    while(t--)
    {
        solve();    
    }
    return 0;
}

对于单点修改操作,直接去每个位置修改即可。对于区间查询大于等于 v 的个数操作,考虑每次单点修改后,对这个块排序,那么单点修改的复杂度就是 的了。那么在保证每个块内有序后,对于一个区间,中间被完全包含的块就可以暴力枚举,每次通过二分查找大于等于 v 的个数。而对于这个区间左右剩下的散点,因为每个块的大小也是 的,所以可以直接去暴力计算大于等于 v 的个数。那么两个操作的复杂度,就都是 的了。

实现时,考虑定义 blen 为每个块的长度,bnum 为块数,注意要向上取整。再定义 为 i 位置所在的块的编号, 和 分别为第 i 个块的左右边界。在查询时,注意若区间在同一个块里,那么就直接暴力遍历即可。

2. 教主的魔法

cpp 复制代码
#include <bits/stdc++.h>
using namespace std;

/*   /\_/\
*   (= ._.)
*   / >  \>
*/

/*
*想好再写
*注意审题 注意特判
*不要红温 不要急躁 耐心一点
*WA了不要立马觉得是思路不对 先耐心找反例
*/

#define endl '\n'
#define dbg(x) cout<<#x<<endl;cout<<x<<endl;
#define vdbg(a) cout<<#a<<endl;for(auto x:a)cout<<x<<" ";cout<<endl;
#define YES cout<<"YES"<<endl;return ;
#define Yes cout<<"Yes"<<endl;return ;
#define NO cout<<"NO"<<endl;return ;
#define No cout<<"No"<<endl;return ;
typedef long long ll;
typedef long double ld;
typedef pair<int,int> pii;
typedef pair<ll,ll> pll;
const int INF=1e9;
const ll INFLL=1e18;
const int dx[]={-1,1,0,0};
const int dy[]={0,0,-1,1};
const int ddx[]={-2,-1,1,2,2,1,-1,-2};
const int ddy[]={1,2,2,1,-1,-2,-2,-1};

void solve()
{
    int n,q;
    cin>>n>>q;
    vector<ll>a(n+1);
    for(int i=1;i<=n;i++)
    {
        cin>>a[i];
    }

    int blen=sqrt(n);
    int bnum=(n+blen-1)/blen;
    vector<int>bi(n+1);
    for(int i=1;i<=n;i++)
    {
        bi[i]=(i-1)/blen+1;
    }
    vector<int>bl(bnum+1);
    vector<int>br(bnum+1);
    for(int i=1;i<=bnum;i++)
    {
        bl[i]=(i-1)*blen+1;
        br[i]=min(n,i*blen);
    }

    vector<int>sorted(n+1);
    for(int i=1;i<=n;i++)
    {
        sorted[i]=a[i];
    }
    for(int i=1;i<=bnum;i++)
    {
        sort(sorted.begin()+bl[i],sorted.begin()+br[i]+1);
    }

    vector<ll>lazy(bnum+1);

    auto bs=[&](int i,int v)->int
    {
        int l=bl[i];
        int r=br[i];
        int m;
        int ans=r+1;
        while(l<=r)
        {
            m=l+r>>1;
            if(sorted[m]>=v)
            {
                ans=m;
                r=m-1;
            }
            else
            {
                l=m+1;
            }
        }
        return br[i]-ans+1;
    };

    auto add=[&](int l,int r,int v)->void
    {
        if(bi[l]==bi[r])
        {
            for(int i=l;i<=r;i++)
            {
                a[i]+=v;
            }
            for(int i=bl[bi[l]];i<=br[bi[l]];i++)
            {
                sorted[i]=a[i];
            }
            sort(sorted.begin()+bl[bi[l]],sorted.begin()+br[bi[l]]+1);
            return ;
        }
        
        for(int i=l;i<=br[bi[l]];i++)
        {
            a[i]+=v;
        }
        for(int i=bl[bi[l]];i<=br[bi[l]];i++)
        {
            sorted[i]=a[i];
        }
        sort(sorted.begin()+bl[bi[l]],sorted.begin()+br[bi[l]]+1);

        for(int i=bi[l]+1;i<=bi[r]-1;i++)
        {
            lazy[i]+=v;
        }

        for(int i=bl[bi[r]];i<=r;i++)
        {
            a[i]+=v;
        }
        for(int i=bl[bi[r]];i<=br[bi[r]];i++)
        {
            sorted[i]=a[i];
        }
        sort(sorted.begin()+bl[bi[r]],sorted.begin()+br[bi[r]]+1);
    };

    auto query=[&](int l,int r,int v)->int
    {
        if(bi[l]==bi[r])
        {
            int ans=0;
            for(int i=l;i<=r;i++)
            {
                if(a[i]>=v-lazy[bi[l]])
                {
                    ans++;
                }
            }
            return ans;
        }
        
        int ans=0;
        for(int i=l;i<=br[bi[l]];i++)
        {
            if(a[i]>=v-lazy[bi[l]])
            {
                ans++;
            }
        }
        for(int i=bi[l]+1;i<=bi[r]-1;i++)
        {
            ans+=bs(i,v-lazy[i]);
        }
        for(int i=bl[bi[r]];i<=r;i++)
        {
            if(a[i]>=v-lazy[bi[r]])
            {
                ans++;
            }
        }
        return ans;
    };  

    char op;int l,r,v;
    while(q--)
    {
        cin>>op>>l>>r>>v;
        if(op=='M')
        {
            add(l,r,v);
        }
        else
        {
            cout<<query(l,r,v)<<endl;
        }
    }
}

void init()
{
}

signed main()
{
    ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
    int t=1;
    //cin>>t;
    init();
    while(t--)
    {
        solve();    
    }
    return 0;
}

和上个题很类似,就是单点修改改成区间增加了。那么和线段树的处理方法类似,设置一个懒信息数组 lazy,每次区间增加时,对于左右的散点暴力,中间的块直接增加懒信息。在区间查询的时候,块内查询大于等于 v-lazy 的个数即可。

3. 蒲公英

cpp 复制代码
#include <bits/stdc++.h>
using namespace std;

/*   /\_/\
*   (= ._.)
*   / >  \>
*/

/*
*想好再写
*注意审题 注意特判
*不要红温 不要急躁 耐心一点
*WA了不要立马觉得是思路不对 先耐心找反例
*/

#define endl '\n'
#define dbg(x) cout<<#x<<" "<<x<<endl;
#define vdbg(a) cout<<#a<<endl;for(auto x:a)cout<<x<<" ";cout<<endl;
#define YES cout<<"YES"<<endl;return ;
#define Yes cout<<"Yes"<<endl;return ;
#define NO cout<<"NO"<<endl;return ;
#define No cout<<"No"<<endl;return ;
#define popcount __builtin_popcount
using ll=long long;
using i128=__int128;
using ld=long double;
using pii=pair<int,int>;
using pll=pair<ll,ll>;
const int INF=1e9;
const ll INFLL=1e18;
const int dx[]={-1,1,0,0};
const int dy[]={0,0,-1,1};
const int ddx[]={-2,-1,1,2,2,1,-1,-2};
const int ddy[]={1,2,2,1,-1,-2,-2,-1};

void solve()
{
    int n,q;
    cin>>n>>q;
    vector<int>a(n+1);
    for(int i=1;i<=n;i++)
    {
        cin>>a[i];
    }

    vector<int>sorted=a;
    sort(sorted.begin()+1,sorted.end());
    int len=1;
    for(int i=2;i<=n;i++)
    {
        if(sorted[len]!=sorted[i])
        {
            sorted[++len]=sorted[i];
        }
    }
    auto rnk=[&](int v)->int
    {
        int l=1;
        int r=len;
        int m;
        int ans;
        while(l<=r)
        {
            m=l+r>>1;
            if(sorted[m]>=v)
            {
                ans=m;
                r=m-1;
            }
            else
            {
                l=m+1;
            }
        }
        return ans;
    };
    for(int i=1;i<=n;i++)
    {
        a[i]=rnk(a[i]);
    }

    int blen=sqrt(n);
    int bnum=(n+blen-1)/blen;
    vector<int>bi(n+1);
    for(int i=1;i<=n;i++)
    {
        bi[i]=(i-1)/blen+1;
    }
    vector<int>bl(bnum+1);
    vector<int>br(bnum+1);
    for(int i=1;i<=bnum;i++)
    {
        bl[i]=(i-1)*blen+1;
        br[i]=min(n,i*blen);
    }

    vector<vector<int>>freq(bnum+1,vector<int>(len+1));
    for(int i=1;i<=bnum;i++)
    {
        for(int j=1;j<=len;j++)
        {
            freq[i][j]=freq[i-1][j];
        }
        
        for(int j=bl[i];j<=br[i];j++)
        {
            freq[i][a[j]]++;
        }
    }

    vector<vector<int>>mode(bnum+1,vector<int>(bnum+1));
    for(int i=1;i<=bnum;i++)
    {
        for(int j=i;j<=bnum;j++)
        {
            int most=mode[i][j-1];
            int mostCnt=freq[j][most]-freq[i-1][most];
            for(int k=bl[j];k<=br[j];k++)
            { 
                int cur=a[k];
                int curCnt=freq[j][cur]-freq[i-1][cur];
                if(curCnt>mostCnt||(curCnt==mostCnt&&cur<most))
                {
                    most=cur;
                    mostCnt=curCnt;
                }
            }
            mode[i][j]=most;
        }
    }

    vector<int>cnts(len+1);

    int l,r;
    int pre=0;
    while(q--)
    {
        cin>>l>>r;
        l=(l+pre-1)%n+1;
        r=(r+pre-1)%n+1;
        if(l>r)
        {
            swap(l,r);
        }

        if(bi[l]==bi[r])
        {
            for(int i=l;i<=r;i++)
            {
                cnts[a[i]]++;
            }
            
            int most=0;
            for(int i=l;i<=r;i++)
            {
                if(cnts[a[i]]>cnts[most]||(cnts[a[i]]==cnts[most]&&a[i]<most))
                {
                    most=a[i];
                }
            }
            
            pre=sorted[most];
            cout<<pre<<endl;

            for(int i=l;i<=r;i++)
            {
                cnts[a[i]]=0;
            }
            continue;
        }

        for(int i=l;i<=br[bi[l]];i++)
        {
            cnts[a[i]]++;
        }
        for(int i=bl[bi[r]];i<=r;i++)
        {
            cnts[a[i]]++;
        }
        
        int most=mode[bi[l]+1][bi[r]-1];
        int mostCnt=freq[bi[r]-1][most]-freq[bi[l]][most];
        for(int i=l;i<=br[bi[l]];i++)
        {
            int cur=a[i];
            int curCnt=cnts[cur]+freq[bi[r]-1][cur]-freq[bi[l]][cur];
            if(curCnt>mostCnt||(curCnt==mostCnt&&cur<most))
            {
                most=cur;
                mostCnt=curCnt;
            }
        }
        for(int i=bl[bi[r]];i<=r;i++)
        {
            int cur=a[i];
            int curCnt=cnts[cur]+freq[bi[r]-1][cur]-freq[bi[l]][cur];
            if(curCnt>mostCnt||(curCnt==mostCnt&&cur<most))
            {
                most=cur;
                mostCnt=curCnt;
            }
        }

        pre=sorted[most];
        cout<<pre<<endl;

        for(int i=l;i<=br[bi[l]];i++)
        {
            cnts[a[i]]=0;
        }
        for(int i=bl[bi[r]];i<=r;i++)
        {
            cnts[a[i]]=0;
        }
    }
}

/*
6 3
1 2 3 2 1 2
1 5
3 6
1 5

1
2
1
*/

void init()
{
}

signed main()
{
    ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
    int t=1;
    //cin>>t;
    init();
    while(t--)
    {
        solve();    
    }
    return 0;
}

对于众数这个问题,首先可以考虑维护 freqij 表示前 i 块里 j 这个数出现的次数,即一个前缀词频。之后,再维护一个 modeij 表示第 i 块到第 j 块之间的最小众数。那么对于查询,中间的整块就可以使用这两个数组查出众数和词频。然后对于两边的散块,就可以暴力统计词频,判断是否大于中间这个众数即可。

构建时,freq 数组就是正常统计前缀词频即可。对于 mode 数组,就是每次先找出 i 到 j-1 块的众数和词频,然后在遍历当前块的时候,对于块内的每个数判断其区间词频是否可以更新众数即可。

总结

分块,暴力的神!

END

相关推荐
路灯上の马陈2 小时前
数据结构-树
数据结构·1024程序员节
澳鹏Appen2 小时前
澳鹏重磅发布ExamOCRBench:面向端到端试卷判分的多任务基准
算法·benchmark·基准评测
必须得开心呀2 小时前
字节序大小端科普
c++·程序人生
benjormin2 小时前
Java数据结构 二叉搜索树
数据结构·算法
人工智能培训2 小时前
知识图谱:结构化表达实体与关系的知识组织技术
大数据·人工智能·算法·重构·生活·知识图谱
会编程的小孩3 小时前
C++ 子集
c++
地平线开发者3 小时前
QAT 实战:从 PyTorch 模型到 J6E 实板精度与延时验证
pytorch·算法·自动驾驶
IT星宿3 小时前
github-gravity:用 Rapier2D 把 GitHub 贡献图做成 SVG 物理动画
算法·github
SunkingYang3 小时前
C++20 vs C++17:新特性对比与用法指南
c++·c++20·特性·版本·新特性·对比·c++17