数列分块入门2

题目链接
给出一个长为 n 的数列,以及 n 个操作,操作涉及区间加法,询问区间内小于某个值 xxx 的元素个数。
根据数列分块入门1的思想,把n个数分为根号n块,不完整块的去暴力求和和查找,完整块用加法标进行标记,查找排序后进行二分查找。

#include<stdio.h>
#include<algorithm>
#include<iostream>
#include<queue>
#include<cmath>
#include<vector>
#include<set>
#include<map>
#include<string.h>
#include<string>
using namespace std;
typedef long long int ll;
const int N=500+10;
int M,n;
vector<int>s[N];
int pos[N*100],a[N*100],tag[N*100];
void uppdate(int x)
{
    s[x].clear();//首先清空块x内的元素
    for(int i=(x-1)*M+1;i<=min(x*M,n);i++)
        s[x].push_back(a[i]);//然后更新块x内的元素
    sort(s[x].begin(),s[x].end());//然后再进行排序
}
void add(int l,int r,int c)
{
    for(int i=l;i<=min(pos[l]*M,r);i++)//块l内的元素(r在块l内)进行暴力加和
        a[i]+=c;
    uppdate(pos[l]);//更新块l内的元素
    if(pos[l]!=pos[r])//若果r不在块l内
    {
        for(int i=(pos[r]-1)*M+1;i<=r;i++)//块r内的元素进行暴力求和
        a[i]+=c;
        uppdate(pos[r]);//更新块r
    }
    for(int i=pos[l]+1;i<=pos[r]-1;i++)
        tag[i]+=c;//整块内的元素进行加法标记
}
int query(int l,int r,int c)
{
    int sum=0;
    for(int i=l;i<=min(pos[l]*M,r);i++)//暴力查找块l内比c小的元素(r有可能在块l内)
        if(a[i]+tag[pos[l]]<c)sum++;
    if(pos[l]!=pos[r])//如果r不在块l内,暴力查找块r内比c小的元素
    {
        for(int i=(pos[r]-1)*M+1;i<=r;i++)
            if(a[i]+tag[pos[r]]<c)sum++;
    }
    for(int i=pos[l]+1;i<=pos[r]-1;i++)//整块内的元素进行二分查找,首先要减去块内的加法标记
    {
         int x=c-tag[i];
         sum+=lower_bound(s[i].begin(),s[i].end(),x)-s[i].begin();
    }
    return sum;
}
int main()
{
    scanf("%d",&n);
    M=sqrt(n);//分成M块
    for(int i=1;i<=n;i++)scanf("%d",a+i);
    for(int i=1;i<=n;i++)
    {
        pos[i]=(i-1)/M+1;//每个位置对应的第几块
        s[pos[i]].push_back(a[i]);//每块的元素
    }
    for(int i=1;i<=pos[n];i++)
        sort(s[i].begin(),s[i].end());//每个块首先进行排序
    for(int i=1;i<=n;i++)
    {
        int l,r,f,c;
        scanf("%d%d%d%d",&f,&l,&r,&c);
        if(f==0)add(l,r,c);
        if(f==1)printf("%d\n",query(l,r,c*c));
    }
    return 0;
}

猜你喜欢

转载自blog.csdn.net/pandauncle/article/details/80709293