数列分块入门1--LOJ

数列分块入门 1

题目链接https://loj.ac/problem/6277

 

内存限制:256 MiB时间限制:100 ms

题目描述

给出一个长为 n 的数列,以及  n个操作,操作涉及区间加法,单点查值。

输入格式

第一行输入一个数字 。

第二行输入 n 个数字,第 i 个数字为 ai,以空格隔开。

接下来输入n  行询问,每行输入四个数字 opt,l,r,c以空格隔开。

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

若opt =1 ,表示询问 ar 的值(l 和 c 忽略)。

输出格式

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

样例

样例输入

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

样例输出

2
5

数据范围与提示

对于 100% 的数据1<=n<=50000,-2^31<=others,ans<=2^31 -1.


。。。很显然这一题我们可以用树状数组或者线段树来做,但既然要练习分块。。。

分块实际上就是暴力枚举,只不过他是优化了的暴力。我们先将1----n分为sqrt(n)个大小为sqrt(n)的块,当然,对于不能完全平方的,我们需要将块数+1。还需要做的就是将每个块的左右端点记录一下:

int t=sqrt(n);
for (int i=1; i<=t; i++){
    L[i]=(i-1)*t+1;
    R[i]=i*t;
}
if (R[t]<n) t++,L[t]=R[t-1]+1,R[t]=n;

然后我们将1---n的所有数打上标记(即所属的块是哪一个)。接下来进行区间操作的时候我们可以利用线段树lazy标记的思想,将l和r所属的块单独更新一下,id(l)+1到id(r)-1的块我们记录一下这个块更新了就好了:

int p=id[l],q=id[r];
for (int i=p+1; i<=q-1; i++) add[i]+=c;
for (int i=l ; i<=R[p]; i++) a[i]+=c;
for (int i=L[q]; i<=r; i++) a[i]+=c;

那么最后的答案就是a[r]+add[id[r]],即它本身的值加上他所属的块所加上的值

以下是AC代码:

#include <bits/stdc++.h>
using namespace std;

const int mac=5e4+10;

inline void in(int &read)
{
    int x=0;
    char ch=getchar();
    while (ch<'0' || ch>'9') ch=getchar();
    while (ch>='0' && ch<='9') x=(x<<3)+(x<<1)+ch-'0',ch=getchar();
    read=x;
}

inline void out(int x)
{
    if (x>=10){
        out(x/10);
    }
    putchar(x%10+'0');
}

int a[mac],L[mac],R[mac],id[mac],add[mac];

void update(int l,int r,int c)
{
    int p=id[l],q=id[r];
    if (p==q)
        for (int i=l; i<=r; i++) a[i]+=c;
    else {
        for (int i=p+1; i<=q-1; i++) add[i]+=c;
        for (int i=l ; i<=R[p]; i++) a[i]+=c;
        for (int i=L[q]; i<=r; i++) a[i]+=c;
    }
}

int main()
{
    int n;
    in(n);
    for (int i=1; i<=n; i++)
        in(a[i]);
    int t=sqrt(n);
    for (int i=1; i<=t; i++){
        L[i]=(i-1)*t+1;
        R[i]=i*t;
    }
    if (R[t]<n) t++,L[t]=R[t-1]+1,R[t]=n;
    for (int i=1; i<=t; i++)
        for (int j=L[i]; j<=R[i]; j++)
            id[j]=i;
    for (int i=1; i<=n; i++){
        int opt,l,r,c;
        in(opt); in(l); in(r); in(c);
        if (!opt) {
            update(l,r,c);
        }
        else {
            out(a[r]+add[id[r]]);
            putchar('\n');
        }
    }
    return 0;
}

 

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值