LOJ——#6277. 数列分块入门 1

无人久伴 提交于 2020-05-04 06:44:25

~~推荐播客~~

「分块」数列分块入门1 – 9 by hzwer

浅谈基础根号算法——分块

博主蒟蒻,有缘人可直接观摩以上大佬的博客。。。

 

#6277. 数列分块入门 1

题目大意:

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

分块入门,区间修改+单点查询

所谓分块,实际上是一种优美的暴力,算是一种数据结构吧。。。
将区间分成许多大小相同的块,对于多出来的部分暴力去做,一般块的大小为$√n$

看了大佬们的代码,有点儿懵逼=_=
来模拟一下流程吧,可能会清晰一点儿

 

 

如图所示,暴力地将一段长度为$10$的序列分成了$4$块,其中$3$块是大小为$√n$的块。
每个数所属块的标号显然是$bl[i]=(i-1)/blo+1$,其中$blo$为块的大小(手推一下即可)
假如你暴力修改某个区间,假设为$[2,7]$,(算了,自己暴力模拟吧
你首先要做的是暴力修改小区间,此时小区间为$[2.3]$和$[7,7]$

这里小区间一般有两个,即最左边不在整个块中的数,和最右边不在整个块中的数

手推一下,暴力修改的左边区间为$[l,min(bl[l]*blo],r)$
emmmmm,我知道暴力修改左边区间的右端点就是$bl[l]*blo]$,为什么还要和给定修改的区间右端点去$min$呢?
。。。思考一下?(大佬说显然嘛
有可能这个待修改区间$[l,r]$就在一个块中。

暴力修改的右区间左短点为$(bl[r]-1)*blo+1$
在这之前要特判一下$bl[l]==bl[r]$,也是防止待修改区间$[l,r]$在一个块中的情况

最后,当然是对完整的块的修改,范围是$bl[l+1],bl[r]-1$,这时修改的是区间加法标记

 
#include<bits/stdc++.h>

#define N 1010100
using namespace std;

int n,a[N],blo[N],atag[N];

void update(int l,int r,int c){
    for(int i=l;i<=min(blo[l]*blo[0],r);i++)
        a[i]+=c;
    if(blo[l]!=blo[r])
        for(int i=(blo[r]-1)*blo[0]+1;i<=r;i++)
            a[i]+=c;
    for(int i=blo[l]+1;i<=blo[r]-1;i++)
        atag[i]+=c;
}

int main()
{
    scanf("%d",&n);
    for(int i=1;i<=n;i++) scanf("%d",&a[i]);
    blo[0]=sqrt(n);
    for(int i=1;i<=n;i++) blo[i]=(i-1)/blo[0]+1;//每一个数所在的块
    for(int i=1;i<=n;i++){
        int opt,l,r,c;
        scanf("%d%d%d%d",&opt,&l,&r,&c);
        if(!opt) update(l,r,c);
        else printf("%d\n",a[r]+atag[blo[r]]);
    }return 0;
}

 

 

#6278. 数列分块入门 2

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

 

跟随大佬的思路,既然一个题你要考虑用分块去做,那么你要考虑以下$3$项

1.不完整的块如何处理?

2.整块如何处理?

3.预处理什么信息?

 

此题关键在于查找区间内小于$x$的元素个数,倘若这个区间不是有序的,难道要暴力查询吗?(暴力只能针对于不完整的块)

那么就必须让他完整的块变得有序,然后二分查找这个快里$<x$的数的个数即可

 

修改完成之后,边上的那两个块不就部分改变了吗,怎么维护呢?

当然还是暴力,暴力清除块,在暴力添加回去。

 

$vector$大法好

#include<iostream>
#include<cstdio>
#include<cmath>
#include<vector>
#include<algorithm>

#define N 500056
using namespace std;

int bl[N],n,v[N],atag[N],blo;
vector<int>V[N];

void update(int x){//第x个块 
    V[x].clear();
    for(int i=(x-1)*blo+1;i<=min(x*blo,n);i++)
        V[x].push_back(v[i]);
    sort(V[x].begin(),V[x].end());
}

void add(int l,int r,int val){
    for(int i=l;i<=min(bl[l]*blo,r);i++)
        v[i]+=val;
    update(bl[l]);
    if(bl[l]!=bl[r]){
        for(int i=(bl[r]-1)*blo+1;i<=r;i++)
            v[i]+=val;
        update(bl[r]);
    }
    for(int i=bl[l]+1;i<=bl[r]-1;i++)
        atag[i]+=val;
}

int query(int l,int r,int c){
    int ans=0;
    for(int i=l;i<=min(bl[l]*blo,r);i++)
        if(v[i]+atag[bl[l]]<c) ++ans;
    if(bl[l]!=bl[r]){
        for(int i=(bl[r]-1)*blo+1;i<=r;i++)
            if(v[i]+atag[bl[r]]<c) ++ans;
    }
    for(int i=bl[l]+1;i<=bl[r]-1;i++){
        int x=c-atag[i];
        ans+=lower_bound(V[i].begin(),V[i].end(),x)-V[i].begin();
    }
    return ans;
}

int main()
{
    scanf("%d",&n);
    blo=sqrt(n);
    for(int i=1;i<=n;i++){
        bl[i]=(i-1)/blo+1;//是除以这个块的大小 
        scanf("%d",&v[i]);
        V[bl[i]].push_back(v[i]);
    }
    for(int i=1;i<=bl[n];i++) 
        sort(V[i].begin(),V[i].end());
    for(int opt,l,r,c,i=1;i<=n;i++){
        scanf("%d%d%d%d",&opt,&l,&r,&c);
        if(!opt) add(l,r,c);
        else printf("%d\n",query(l,r,c*c));
    }
    
    return 0;
}

 

#6279. 数列分块入门 3

 

我只能说这些骚操作我从来没见过,仿佛打开了异世界的大门,(大佬们早就进去看了不知多少遍了

题目大意:

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

 

上一道题是$vector$二分查找,反之就是$STL$,而这一道也是这样的,不过用的是$set$

 

hzwer(他想真实表达的东西):

可以在块内维护其它结构使其更具有拓展性,比如放一个 $set$ ,这样如果还有插入、删除元素的操作,会更加的方便。

即分块套其他数据结构,%%%%强无敌呀!!

 

分块的调试检测技巧:

可以生成一些大数据,然后用两份分块大小不同的代码来对拍,还可以根据运行时间尝试调整分块大小,减小常数。——hzwer

 

#include<iostream>
#include<cstdio>
#include<cmath>
#include<vector>
#include<set>
#include<algorithm>

#define N 500056
using namespace std;

int bl[N],n,v[N],atag[N],blo;
set<int>S[N];

void add(int l,int r,int val){
    for(int i=l;i<=min(bl[l]*blo,r);i++){
        S[bl[l]].erase(v[i]);
        v[i]+=val;
        S[bl[l]].insert(v[i]);
    } 
    if(bl[l]!=bl[r])
        for(int i=(bl[r]-1)*blo+1;i<=r;i++){
            S[bl[r]].erase(v[i]);
            v[i]+=val;
            S[bl[r]].insert(v[i]);
        }
    for(int i=bl[l]+1;i<=bl[r]-1;i++)
        atag[i]+=val;
}

int query(int l,int r,int val){
    int ans=-1;
    for(int i=l;i<=min(bl[l]*blo,r);i++)
        if(v[i]+atag[bl[l]]<val) ans=max(ans,v[i]+atag[bl[l]]);
    if(bl[l]!=bl[r]){
        for(int i=(bl[r]-1)*blo+1;i<=r;i++)
            if(v[i]+atag[bl[r]]<val) ans=max(ans,v[i]+atag[bl[r]]);
    }
    for(int i=bl[l]+1;i<=bl[r]-1;i++){
        int x=val-atag[i];
        set<int>::iterator it=S[i].lower_bound(x);//二分查找
        if(it==S[i].begin()) continue;
        --it;
        ans=max(ans,*it+atag[i]);
    }
    return ans;
}

int main()
{
    scanf("%d",&n);
    blo=sqrt(n);
    for(int i=1;i<=n;i++){
        bl[i]=(i-1)/blo+1;//是除以这个块的大小 
        scanf("%d",&v[i]);
        S[bl[i]].insert(v[i]);
    }
    for(int opt,l,r,c,i=1;i<=n;i++){
        scanf("%d%d%d%d",&opt,&l,&r,&c);
        if(!opt) add(l,r,c);
        else printf("%d\n",query(l,r,c));
    }
    return 0;
}

 

#6280. 数列分块入门 4

给出一个长为 $n$ 的数列,以及 $n$ 个操作,操作涉及区间加法,区间求和。

 

现在看着道题,你会说,这不就是线段树裸题吗,水水水水!

当你做了分块前$3$道题目后,看到这道题,你也会说,这不是分块裸题吗,随便做。

 

%%%%%(为什么我都说不出来呢?

交了5遍才A掉它,太菜啦,太菜啦。。。

 

有两个细节,放在代码里了。

#include<iostream>
#include<cstdio>
#include<cmath>
#include<vector>
#include<set>
#include<algorithm>

#define N 500056
#define LL long long
using namespace std;

LL bl[N],n,v[N],atag[N],blo,sum[N];

void add(LL l,LL r,LL val){
    for(LL i=l;i<=min(bl[l]*blo,r);i++)
        v[i]+=val,sum[bl[l]]+=val;//修改不完整的块时那个块的区间总和也随之改变 
    if(bl[l]!=bl[r])
        for(LL i=(bl[r]-1)*blo+1;i<=r;i++)
            v[i]+=val,sum[bl[r]]+=val;
    for(LL i=bl[l]+1;i<=bl[r]-1;i++)
        atag[i]+=val; 
}
LL query(LL l,LL r,LL mod){
    LL ans=0;
    for(LL i=l;i<=min(bl[l]*blo,r);i++)
        ans=(ans%mod+(v[i]+atag[bl[l]])%mod)%mod;
    if(bl[l]!=bl[r])
        for(LL i=(bl[r]-1)*blo+1;i<=r;i++)
            ans=(ans%mod+(v[i]+atag[bl[r]])%mod)%mod;
    for(LL i=bl[l]+1;i<=bl[r]-1;i++)
        ans=(ans%mod+(sum[i]+atag[i]*blo)%mod)%mod;//标记不要误写成bl[i] 
    return ans%mod;
}

int main()
{
    scanf("%lld",&n);
    blo=sqrt(n);
    for(LL i=1;i<=n;i++){
        bl[i]=(i-1)/blo+1;//是除以这个块的大小 
        scanf("%lld",&v[i]);
        sum[bl[i]]+=v[i];
    }
    for(LL opt,l,r,c,i=1;i<=n;i++){
        scanf("%lld%lld%lld%lld",&opt,&l,&r,&c);
        if(!opt) add(l,r,c);
        else printf("%lld\n",query(l,r,c+1));
    }
    return 0;
}

 

#6281. 数列分块入门 5

给出一个长为 $n$ 的数列 $a_1\ldots a_n$ ,以及 $n$ 个操作,操作涉及区间开方,区间求和。

 

区间开方?操作很骚啊。。。(就是不会

 

不难发现,这题的修改就只有下取整开方,而一个数经过几次开方之后,它的值就会变成 0 或者 1。——hzwer

 

然后就可以随便搞啦。。。。(详情见代码

#include<bits/stdc++.h>

#define N 5000000
using namespace std;

int n,bl[N],v[N],sum[N],blo;
bool flg[N];

void change_x(int x){
    if(flg[x]) return;
    flg[x]=1;
    sum[x]=0;
    for(int i=(x-1)*blo+1;i<=x*blo;i++){
        v[i]=sqrt(v[i]);
        if(v[i]>1) flg[x]=0;
        sum[x]+=v[i];
    }
}

void change(int l,int r){
    for(int i=l;i<=min(bl[l]*blo,r);i++)
        sum[bl[l]]-=v[i],v[i]=sqrt(v[i]),sum[bl[l]]+=v[i];
    if(bl[l]!=bl[r]){
        for(int i=(bl[r]-1)*blo+1;i<=min(r,n);i++){
            sum[bl[r]]-=v[i],v[i]=sqrt(v[i]),sum[bl[r]]+=v[i];
        }
    }
    for(int i=bl[l]+1;i<=bl[r]-1;i++)
        change_x(i);
}
int query(int l,int r){
    int ans=0;
    for(int i=l;i<=min(bl[l]*blo,r);i++)
        ans+=v[i];
    if(bl[l]!=bl[r]){
        for(int i=(bl[r]-1)*blo+1;i<=r;i++)
            ans+=v[i];
    }
    for(int i=bl[l]+1;i<=bl[r]-1;i++)
        ans+=sum[i];
    return ans;
}

int main()
{
    scanf("%d",&n);
    blo=sqrt(n);
    for(int i=1;i<=n;i++){
        scanf("%d",&v[i]);
        bl[i]=(i-1)/blo+1;
        sum[bl[i]]+=v[i];
    }
    for(int opt,l,r,c,i=1;i<=n;i++){
        scanf("%d%d%d%d",&opt,&l,&r,&c);
        if(!opt) change(l,r);
        else printf("%d\n",query(l,r));
    }
    
    return 0;
}

 

P4145 上帝造题的七分钟2 / 花神游历各国

有点儿坑的地方是$l$有可能大于$r$,所以要$swap(l,r)$

 

#include<bits/stdc++.h>

#define LL long long
#define N 1000000
using namespace std;

LL n,m,v[N],sum[N],bl[N],blo;
bool flg[N];

void change_sqrt(LL x) {
    if(flg[x]) return;
    flg[x]=1;
    sum[x]=0;
    for(LL i=(x-1)*blo+1; i<=x*blo; i++) {
        v[i]=sqrt(v[i]),sum[x]+=v[i];
        if(v[i]>1) flg[x]=0;
    }
}

void add(LL l, LL r) {
    if(l>r) swap(l,r);
    for(LL i=l; i<=min(bl[l]*blo,r); i++)
        sum[bl[l]]-=v[i],v[i]=sqrt(v[i]),sum[bl[l]]+=v[i];
    if(bl[l]!=bl[r]) {
        for(LL i=(bl[r]-1)*blo+1; i<=r; i++) {
            sum[bl[r]]-=v[i],v[i]=sqrt(v[i]),sum[bl[r]]+=v[i];
        }
    }
    for(LL i=bl[l]+1; i<=bl[r]-1; i++)
        change_sqrt(i);
}

inline LL query(LL l,LL r) {
    if(l>r) swap(l,r);
    LL ans=0;
    for(LL i=l; i<=min(bl[l]*blo,r); i++)
        ans+=v[i];
    if(bl[l]!=bl[r]) {
        for(LL i=(bl[r]-1)*blo+1; i<=r; i++)
            ans+=v[i];
    }
    for(LL i=bl[l]+1; i<=bl[r]-1; i++)
        ans+=sum[i];
    return ans;
}

int main() {
    scanf("%lld",&n);
    blo=sqrt(n);
    for(LL i=1; i<=n; i++) {
        scanf("%lld",&v[i]);
        bl[i]=(i-1)/blo+1;
        sum[bl[i]]+=v[i];
    }
    scanf("%lld",&m);
    for(LL opt,l,r,i=1; i<=m; i++) {
        scanf("%lld%lld%lld",&opt,&l,&r);
        if(!opt) add(l,r);
        else printf("%lld\n",query(l,r));
    }

    return 0;
}
分块

 

#6282. 数列分块入门 6

给出一个长为 $n$ 的数列,以及 $n$ 个操作,操作涉及单点插入,单点询问,数据随机生成。

 

暴力插入,块重构,当元一个块内素数量很多时,重新构造

 

$vector \&\& pair$

#include<iostream>
#include<cstdio>
#include<cmath>
#include<algorithm>
#include<vector>

#define N 200005
using namespace std;

int n,v[N],st[N],blo,m;
vector<int>ve[1005];

pair<int,int>query(int x){
    int t=1;
    while(ve[t].size()<x){
        x-=ve[t].size();++t;
    }
    return make_pair(t,x-1);
}

void rebuild(){
    int top=0;
    for(int i=1;i<=m;i++){
        for(vector<int>::iterator j=ve[i].begin();j!=ve[i].end();j++)
            st[++top]=*j;
        ve[i].clear();
    }
    int blo2=sqrt(top);
    for(int i=1;i<=top;i++)
        ve[(i-1)/blo2+1].push_back(st[i]);
    m=(top-1)/blo2+1;
}

void insert(int a,int b){
    pair<int,int> t=query(a);
    ve[t.first].insert(ve[t.first].begin()+t.second,b);
    if(ve[t.first].size()>20*blo)
        rebuild();
}

int main()
{
    scanf("%d",&n);blo=sqrt(n);
    for(int i=1;i<=n;i++){
        scanf("%d",&v[i]);
        ve[(i-1)/blo+1].push_back(v[i]);
    }
    m=(n-1)/blo+1;//统计一共有多少块
    for(int opt,l,r,c,i=1;i<=n;i++){
        scanf("%d%d%d%d",&opt,&l,&r,&c);
        if(!opt) insert(l,r);
        else {
            pair<int,int>t=query(r);
            printf("%d\n",ve[t.first][t.second]);
        }
    }
    
    return 0;
}

 

 

#6283. 数列分块入门 7

 

区间乘法+区间加法+单点查询

修改时,对边上的块直接下放标记,乘先加后

 

#include<bits/stdc++.h>

#define N 200005
#define mod 10007
using namespace std;

int n,v[N],mtag[N],atag[N],bl[N],blo;

void reserve(int x) {
    for(int i=(x-1)*blo+1; i<=min(x*blo,n); i++)
        v[i]=(v[i]*mtag[x]%mod+atag[x]%mod)%mod;
    atag[x]=0,mtag[x]=1;
}

void add(int f,int l,int r,int c) {
    reserve(bl[l]);
    for(int i=l; i<=min(bl[l]*blo,r); i++) {
        if(f) v[i]=v[i]*c%mod;
        else v[i]=(v[i]+c)%mod;
    }
    if(bl[l]!=bl[r]) {
        reserve(bl[r]);
        for(int i=(bl[r]-1)*blo+1; i<=r; i++)
            if(f) v[i]=v[i]*c%mod;
            else v[i]=(v[i]+c)%mod;
    }
    for(int i=bl[l]+1; i<=bl[r]-1; i++) {
        if(f) atag[i]=atag[i]*c%mod,mtag[i]=mtag[i]*c%mod;
        else atag[i]=(atag[i]+c)%mod;
    }
}

int main() {
    scanf("%d",&n);
    blo=sqrt(n);
    for(int i=1; i<=n; i++) {
        scanf("%d",&v[i]);
        bl[i]=(i-1)/blo+1;
        mtag[bl[i]]=1;
    }
    for(int opt,l,r,c,i=1; i<=n; i++) {
        scanf("%d%d%d%d",&opt,&l,&r,&c);
        if(opt==2) printf("%d\n",(v[r]*mtag[bl[r]]%mod+atag[bl[r]]%mod)%mod);
        else add(opt,l,r,c);
    }

    return 0;
}

 

P3373 【模板】线段树 2

自己的分块自带大常数,qwq,只能做$70$分,还是太弱啦。。。

// luogu-judger-enable-o2
# pragma GCC optimize "O3"
#include<bits/stdc++.h>

#define N 200005
#define LL long long
#define IL inline
#define RG register
using namespace std;

IL void in(RG LL &x){
    register char c=getchar();x=0;int f=1;
    while(!isdigit(c)){if(c=='-') f=-1;c=getchar();}
    while(isdigit(c)){x=x*10+c-'0';c=getchar();}
    x*=f;
}

IL void print(RG LL x){
    if(x>9) print(x/10);
    putchar(x%10+'0');
}
LL n,v[N],mtag[N],atag[N],bl[N],blo,mod,m,sum[N];

IL void reserve(RG LL x) {
    for(LL i=(x-1)*blo+1; i<=min(x*blo,n); i++)
        v[i]=(v[i]*mtag[x]%mod+atag[x]%mod)%mod;
    atag[x]=0,mtag[x]=1;
}

IL void add(RG LL f,RG LL l,RG LL r,RG LL c) {
    reserve(bl[l]);
    for(RG LL i=l; i<=min(bl[l]*blo,r); i++) {
        if(f==1) sum[bl[l]]+=v[i]*(c-1)%mod,v[i]=v[i]*c%mod;
        else v[i]=(v[i]+c)%mod,sum[bl[l]]+=c;
        sum[bl[l]]%=mod;
    }
    if(bl[l]!=bl[r]) {
        reserve(bl[r]);
        for(RG LL i=(bl[r]-1)*blo+1; i<=r; i++) {
            if(f==1) sum[bl[r]]+=v[i]*(c-1)%mod,v[i]=v[i]*c%mod;
            else v[i]=(v[i]+c)%mod,sum[bl[r]]+=c;
            sum[bl[r]]%=mod;
        }
        for(RG LL i=bl[l]+1; i<=bl[r]-1; i++) {
            if(f==1) atag[i]=atag[i]*c%mod,mtag[i]=mtag[i]*c%mod,sum[i]=c*sum[i]%mod;
            else atag[i]=(atag[i]+c)%mod,sum[i]=(sum[i]+blo*c)%mod;
        }
    }
}

IL LL query(RG LL l,RG LL r) {
    RG LL ans=0;
    for(RG LL i=l; i<=min(bl[l]*blo,r); i++)
        ans=(ans+v[i]*mtag[bl[l]]%mod+atag[bl[l]]%mod)%mod;
    if(bl[l]!=bl[r]) {
        for(RG LL i=(bl[r]-1)*blo+1; i<=r; i++)
            ans=(ans+v[i]*mtag[bl[r]]%mod+atag[bl[r]]%mod)%mod;
    }
    for(RG LL i=bl[l]+1; i<=bl[r]-1; i++) ans=(ans+sum[i])%mod;

    return ans;
}

int main() {
    in(n),in(m),in(mod);
    blo=sqrt(n);
    for(RG LL i=1; i<=n; i++) {
        in(v[i]);
        bl[i]=(i-1)/blo+1;
        mtag[i]=1;
        sum[bl[i]]=(sum[bl[i]]+v[i])%mod;
    }
    
    for(RG LL opt,l,r,c,i=1; i<=m; i++) {
        in(opt),in(l),in(r);
        if(opt==3) print(query(l,r)%mod),putchar('\n');
        else in(c),add(opt,l,r,c);
    }

    return 0;
}
分块——线段树2

 

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!