分块

其实分块也没学多少。

谈谈一开始对这个算法的理解吧。

分块算法主要是对应区间操作与区间 / 单点查询。所谓分块就是将 有 n 个元素的数组分成 n / m 块, 一般 msqrt(n)。

一般操作的区间会被划分成三块, [L, L所在块的右界] (不完整的块), 很多个被划分的块(完整的块),[R所在块的左界,R](不完整的块)

对于不完整的块是进行暴力操作,对于完整的块是对其打上需要的标记,到查询的时候再将标记操作上。

一般来说这样会使得原本时间复杂度为O(n)的操作降到O(sqrt(n)),从而达到题目要求。

附上HZWER的分块九讲

分块一:传送门 

简要题意:对于一个长度为n的数列进行区间加法修改和单点询问。

复制代码
#include <bits/stdc++.h>
using namespace std;
#define mod 998244353
#define pi acos(-1)
#define inf 0x7fffffff
#define ll long long
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;
}
int n, blo_sz;
int val[50005], bl_num[50005], atag[50005];
void add(int a, int b, int c) {
    for(int i=a;i<=min(bl_num[a]*blo_sz,b);i++) //将a到min(a所在块的right, b)暴力加上c
        val[i]+=c;
    if(bl_num[a]!=bl_num[b])
        for(int i=(bl_num[b]-1)*blo_sz+1;i<=b;i++) //将(b所在块的left, b)暴力加上c
            val[i]+=c;
    for(int i=bl_num[a]+1;i<=bl_num[b]-1;i++) //给中间部分的块打上标记
        atag[i]+=c;
}
int main()
{
    n=read(); blo_sz = sqrt(n);
    for(int i=1;i<=n;i++) val[i]=read();
    for(int i=1;i<=n;i++)bl_num[i]=(i-1)/blo_sz+1;
    for(int i=1;i<=n;i++) {
        int f=read(),a=read(),b=read(),c=read();
        if(f==0)add(a,b,c);
        if(f==1)printf("%d\n",val[b] + atag[bl_num[b]]);
    }
    return 0;
}
View Code
复制代码

分块二传送门 

简要题意:给出一个长为 n的数列,以及 n 个操作,操作涉及区间加法,询问区间内小于某个值 x的元素个数

这道题主要是维护方式改变了。维护的是一个有序的块,同时发现了他的修改是在原数组上直接暴力修改的。只有块才会打上标记。

(一开始觉得可以用multiset的这样就不用sort了,之后发现set好像是不能很快统计一个数之前有多少个数的,还是vector来的快)

复制代码
#include <bits/stdc++.h>
using namespace std;
#define mod 998244353
#define pi acos(-1)
#define inf 0x7fffffff
#define ll long long
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;
}
int n,blo;
int v[50005],bl[50005],atag[50005];
vector<int>ve[505];
void reset(int x)
{
    ve[x].clear();
    for(int i=(x-1)*blo+1;i<=min(x*blo,n);i++)
        ve[x].push_back(v[i]); //这里的v[i]代表的是原始数组的值,而不是分块之后的块
    sort(ve[x].begin(),ve[x].end());
}
//更新也是分三块
void add(int a,int b,int c)
{
    for(int i=a;i<=min(bl[a]*blo,b);i++)
        v[i]+=c;    //这里也是在原数组上修改,而不是在块内    //突然发现原来的也是在原数组的基础上修改
    reset(bl[a]);   //所以每次修改完之后都需要update块内元素
    if(bl[a]!=bl[b])
    {
        for(int i=(bl[b]-1)*blo+1;i<=b;i++)
            v[i]+=c;
        reset(bl[b]);
    }
    for(int i=bl[a]+1;i<=bl[b]-1;i++)
        atag[i]+=c;
}
//查询也是分三块
int query(int a,int b,int c)
{
    int ans=0;
    for(int i=a;i<=min(bl[a]*blo,b);i++)
        if(v[i]+atag[bl[a]]<c)ans++;
    if(bl[a]!=bl[b])
        for(int i=(bl[b]-1)*blo+1;i<=b;i++)
            if(v[i]+atag[bl[b]]<c)ans++;
    for(int i=bl[a]+1;i<=bl[b]-1;i++)
    {
        int x=c-atag[i];
        ans+=lower_bound(ve[i].begin(),ve[i].end(),x)-ve[i].begin();
    }
    return ans;
}
int main()
{
    n=read();blo=sqrt(n);
    for(int i=1;i<=n;i++)v[i]=read();
    for(int i=1;i<=n;i++)
    {
        bl[i]=(i-1)/blo+1;
        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++)
    {
        int f=read(),a=read(),b=read(),c=read();
        if(f==0)add(a,b,c);
        if(f==1)printf("%d\n",query(a,b,c*c));
    }
    return 0;
}
View Code
复制代码

分块三:传送门 

简要题意:给出一个长为 n 的数列,以及 n 个操作,操作涉及区间加法,询问区间内小于某个值 x 的前驱(比其小的最大元素)

这道题就不需要统计个数了,所以就可以用set去维护。但是记得query的时候

 

复制代码
#include <bits/stdc++.h>
#define mod 998244353
#define pi acos(-1)
#define inf 0x7fffffff
#define ll long long
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;
}
int n,blo;
int val[100005],bl_nm[100005],atag[100005];
set<int> st[105];
void add(int a,int b,int c)
{
    for(int i=a;i<=min(bl_nm[a]*blo,b);i++)
    {
        st[bl_nm[a]].erase(val[i]);
        val[i]+=c;
        st[bl_nm[a]].insert(val[i]);
    }
    if(bl_nm[a]!=bl_nm[b])
    {
        for(int i=(bl_nm[b]-1)*blo+1;i<=b;i++)
        {
            st[bl_nm[b]].erase(val[i]);
            val[i]+=c;
            st[bl_nm[b]].insert(val[i]);
        }
    }
    for(int i=bl_nm[a]+1;i<=bl_nm[b]-1;i++)
        atag[i]+=c;
}
int query(int a,int b,int c)
{
    int ans=-1;
    //取左块最大
    for(int i=a;i<=min(bl_nm[a]*blo,b);i++)
    {
        int temp=val[i]+atag[bl_nm[a]];
        if(temp<c)ans=max(temp,ans);
    }
    //取右块最大
    if(bl_nm[a]!=bl_nm[b])
        for(int i=(bl_nm[b]-1)*blo+1;i<=b;i++)
        {
            int temp=val[i]+atag[bl_nm[b]];
            if(temp<c)ans=max(temp,ans);
        }
    //取中间所有块数的最大
    for(int i=bl_nm[a]+1;i<=bl_nm[b]-1;i++)
    {
        int x=c-atag[i]; //这里是为了不对块内元素进行变化,使得常数变大。
        set<int>::iterator it=st[i].lower_bound(x);
        if(it==st[i].begin())continue; //防止全部都大于
        --it;
        ans=max(ans,*it+atag[i]);
    }
    return ans;
}

int main()
{
    n=read();blo=1000;
    for(int i=1;i<=n;i++)val[i]=read();
    for(int i=1;i<=n;i++)
    {
        bl_nm[i]=(i-1)/blo+1;
        st[bl_nm[i]].insert(val[i]);
    }
    for(int i=1;i<=n;i++)
    {
        int f=read(),a=read(),b=read(),c=read();
        if(f==0)add(a,b,c);
        if(f==1)printf("%d\n",query(a,b,c));
    }
    return 0;
}
View Code
复制代码

 

分块四:传送门 

简要题意: 给出一个长为 n 的数列,以及 n个操作,操作涉及区间加法,区间求和。

相较于分块一,只不过多了一个维护的数组sum,其所代表的意义是块的和。那么左右块直接暴力加,之后加上中间块的sum[i]+block_size*tag[block]就行

复制代码
#include <bits/stdc++.h>
#define mod 998244353
#define pi acos(-1)
#define inf 0x7fffffff
#define ll long long
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;
}
int n,blo;
int bl_nm[50005];
ll val[50005],atag[50005],sum[50005];
void add(int a,int b,int c)
{
    for(int i=a;i<=min(bl_nm[a]*blo,b);i++)
        val[i]+=c,sum[bl_nm[a]]+=c;;
    if(bl_nm[a]!=bl_nm[b])
        for(int i=(bl_nm[b]-1)*blo+1;i<=b;i++)
            val[i]+=c,sum[bl_nm[b]]+=c;
    for(int i=bl_nm[a]+1;i<=bl_nm[b]-1;i++)
        atag[i]+=c;
}
ll query(int a,int b)
{
    ll ans=0;
    for(int i=a;i<=min(bl_nm[a]*blo,b);i++)
        ans+=val[i]+atag[bl_nm[a]];
    if(bl_nm[a]!=bl_nm[b])
        for(int i=(bl_nm[b]-1)*blo+1;i<=b;i++)
            ans+=val[i]+atag[bl_nm[b]];
    for(int i=bl_nm[a]+1;i<=bl_nm[b]-1;i++)
        ans+=sum[i]+blo*atag[i];
    return ans;
}
int main()
{
    n=read();blo=sqrt(n);
    for(int i=1;i<=n;i++)val[i]=read();
    for(int i=1;i<=n;i++)
    {
        bl_nm[i]=(i-1)/blo+1;
        sum[bl_nm[i]]+=val[i];
    }
    for(int i=1;i<=n;i++)
    {
        int f=read(),a=read(),b=read(),c=read();
        if(f==0)add(a,b,c);
        if(f==1)
            printf("%d\n",query(a,b)%(c+1));
    }
    return 0;
}
View Code
复制代码

 

posted @   ViKyanite  阅读(8)  评论(0编辑  收藏  举报
编辑推荐:
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
· SQL Server 2025 AI相关能力初探
阅读排行:
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· winform 绘制太阳,地球,月球 运作规律
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 上周热点回顾(3.3-3.9)
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人
点击右上角即可分享
微信分享提示
主题色彩