分块(真的是优雅的暴力)

M - 一道普通题1

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

Input

第一行输入一个数字 n,1≤n≤100000n,1≤n≤100000。

第二行输入 nn 个非负整数,第 ii个数字为 ai(0≤ai≤109)ai(0≤ai≤109)以空格隔开。

接下来输入nn 行询问,每行输入四个数字 opt、l、r、c(c>0)opt、l、r、c(c>0),以空格隔开。

若 oopt=0opt=0,表示将位于[l,r][l,r]的之间的数字都加 cc。

若 opt=1opt=1,表示询问[l,r][l,r]中 cc 的前驱的值(不存在则输出 −1−1)。

Output

对于每次询问,输出一行一个数字表示答案。

保证所有数据在int范围内

Sample Input

4
1 2 2 3
0 1 3 1
1 1 4 4
0 1 2 2
1 1 2 4

Sample Output

3
-1

Hint

最初一看到这个题的这种格式就准备用树状数组来做的,可是做到询问前驱的时候就发现做不好了,然后就瞄了一下了学长的题解,才发现要用分块做,分块呢我只知道它的模板,~~~(我真的好菜哎^^),然后我就套模板一写,可提交就T了,中间又错了好多次,最后又看了看学长的代码,学长是用的vector来存每一块的数,左右两端残块暴力处理,中间的整块(如果有的话)一块块的处理,这时更新的时候就没有对原数组直接+val,而是维护一个lazy数组,表示每一块要加的数是多少,比较的时候用val-lazy[i]与原数组比较,如果有比它小的最大的数则存在前缀,且为找到的原数组的那个数+lazy[i],否则就没有。。。。。。。最后明白这才叫分块嘛,中间一块一块的处理,像我最初中间还是一个一个点处理,那分块意义何在。。。。。。。

AC代码:

#include<iostream>
#include<cstring>
#include<algorithm>
#include<set>
#include<cmath>
#include<cstdio>
#include<vector>
using namespace std;
const int maxn=100001;
int belong[maxn],l[maxn],r[maxn],b[maxn];
int lazy[maxn];
int block,num;
int n;
vector<int>s[1010];
void build()
{
    block=(int)sqrt(n);
    num=n/block;
    if(n%block) num++;
    for(int i=1;i<=num;i++)
        l[i]=(i-1)*block+1,r[i]=i*block;
    r[num]=n;//
    for(int i=1;i<=n;i++)
    {
        scanf("%d",&b[i]);
        belong[i]=(i-1)/block+1;
        s[belong[i]].push_back(b[i]);
    }
    for(int i=1;i<=belong[n];i++) sort(s[i].begin(),s[i].end());
}
void update(int ll,int rr,int val)
{
    int first=belong[ll];
    int last=belong[rr];
    for(int i=ll;i<=min(belong[ll]*block,rr);i++)
        b[i]+=val;
    s[belong[ll]].clear();
    for(int i=(belong[ll]-1)*block+1;i<=min(belong[ll]*block,n);i++)
        s[belong[ll]].push_back(b[i]);
    sort(s[belong[ll]].begin(),s[belong[ll]].end());//左边残块

    if(belong[ll]==belong[rr]) return;//如果只有一个残块修改完毕

    for(int i=(belong[rr]-1)*block;i<=rr;i++)
        b[i]+=val;
    s[belong[rr]].clear();
    for(int i=(belong[rr]-1)*block+1;i<=min(belong[rr]*block,n);i++)
        s[belong[rr]].push_back(b[i]);
    sort(s[belong[rr]].begin(),s[belong[rr]].end());//右边残块

    for(int i=first+1;i<=last-1;i++)
        lazy[i]+=val;//中间的整块用lazy维护而不对原数组直接+
}
int query(int ll,int rr,int val)
{
    int first=belong[ll];
    int last=belong[rr];
    int _max=-1;
    for(int i=ll;i<=min(r[belong[ll]],rr);i++)
        if(b[i]+lazy[belong[i]]<val)
            _max=max(_max,b[i]+lazy[belong[i]]);
    if(first!=last)
    {
        for(int i=l[belong[rr]];i<=rr;i++)
            if(b[i]+lazy[belong[i]]<val)
              _max=max(b[i]+lazy[belong[i]],_max);
    }
    for(int i=first+1;i<=last-1;i++)
    {
        auto it=lower_bound(s[i].begin(),s[i].end(),val-lazy[i]);
        if(it==s[i].begin()) continue;
        --it;
        _max=max(_max,*it+lazy[i]);
    }
    return _max;
}
int main()
{
    //ios::sync_with_stdio(0),cin.tie(0);
    while(~scanf("%d",&n)){
    build();
    int m=n;
    int op,x,y,k;
    while(m--)
    {
        scanf("%d%d%d%d",&op,&x,&y,&k);
        if(op==0)
        {
            update(x,y,k);
        }
        else if(op==1)
        {
            printf("%d\n",query(x,y,k));
        }
    }
    }
    return 0;
}

最后贴个模板:

分块模板:
belong[]数组表示第i个点属于哪个块,l[],r[]分别表示第i个点所在的左右边界
block表示每块的大小,num表示有多少块
void build()
{
    block=(int)sqrt(n);
    num=n/block;
    if(n%block) num++;
    for(int i=1;i<=num;i++)
        l[i]=(i-1)*block+1,r[i]=i*block;
    r[num]=n;//最后一块的边界一定是n
    for(int i=1;i<=n;i++){
        cin>>a[i];
        belong[i]=(i-1)/block+1;
    }
    //下面维护需要的数组.....
}

猜你喜欢

转载自blog.csdn.net/shaohang_/article/details/81185430
今日推荐