数列分块入门九题(二):LOJ6280~6282

Preface

个人感觉这中间的三题是最水的没有之一


数列分块入门 4——区间加法,区间求和

这个也是很多数据结构完爆的题目线段树入门题,但是练分块我们就要写吗

修改还是与之前类似,只不过我们要维护每一块内元素的和,注意这个要实时更新

这样就可以轻松水过了。

CODE

#include<cstdio>
#include<cctype>
#include<cmath>
using namespace std;
const int N=50005,BLO=250;
int n,a[N],blk[N],size,opt,x,y,z;
long long mark[BLO],sum[BLO];
inline char tc(void)
{
    static char fl[100000],*A=fl,*B=fl;
    return A==B&&(B=(A=fl)+fread(fl,1,100000,stdin),A==B)?EOF:*A++;
}
inline void read(int &x)
{
    x=0; char ch; int flag=1; while (!isdigit(ch=tc())) flag=ch^'-'?1:-1;
    while (x=(x<<3)+(x<<1)+ch-'0',isdigit(ch=tc()));
}
inline void write(int x)
{
    if (x>9) write(x/10);
    putchar(x%10+'0');
}
inline int min(int a,int b)
{
    return a<b?a:b;
}
inline int query(int l,int r,int mod)
{
    register int i; int res=0;
    for (i=l;i<=min(blk[l]*size,r);++i) res=(res+1LL*(a[i]+mark[blk[l]]))%mod;
    if (blk[l]!=blk[r]) for (i=(blk[r]-1)*size+1;i<=r;++i) res=(res+1LL*(a[i]+mark[blk[r]]))%mod;
    for (i=blk[l]+1;i<=blk[r]-1;++i) res=(res+1LL*sum[i])%mod;
    return res;
}
inline void modify(int l,int r,int x)
{
    register int i;
    for (i=l;i<=min(blk[l]*size,r);++i)
    a[i]+=x,sum[blk[l]]+=x;
    if (blk[l]!=blk[r]) for (i=(blk[r]-1)*size+1;i<=r;++i)
    a[i]+=x,sum[blk[r]]+=x;
    for (i=blk[l]+1;i<=blk[r]-1;++i)
    mark[i]+=x,sum[i]+=x*size;
}
int main()
{
    //freopen("4.in","r",stdin); freopen("4.out","w",stdout);
    register int i; read(n); size=(int)sqrt(n);
    for (i=1;i<=n;++i)
    read(a[i]),sum[blk[i]=(i-1)/size+1]+=a[i];
    for (i=1;i<=n;++i)
    {
        read(opt); read(x); read(y); read(z);
        if (opt) write(query(x,y,z+1)),putchar('\n'); else modify(x,y,z);
    }
    return 0;
}

数列分块入门 5——区间开方,区间求和

这道题其实也是一道经典的并查集的题目,但是我们只讲分块。

首先我们要注意到,一个数被进行开方操作至多\(O(log^2n)\)次时它就会变成\(0/1\)

然后我们对于每一个块在维护和的同时,在打上一个标记,当一个块内所有元素都是\(0/1\)时就不更新它。

否则暴力搞一遍即可。最后注意修改时记得把区间和的标记一起维护不能直接开方

CODE

#include<cstdio>
#include<cctype>
#include<cmath>
using namespace std;
const int N=50005,BLO=250;
int n,a[N],blk[N],sum[BLO],size,opt,x,y,z;
bool flag[BLO];
inline char tc(void)
{
    static char fl[100000],*A=fl,*B=fl;
    return A==B&&(B=(A=fl)+fread(fl,1,100000,stdin),A==B)?EOF:*A++;
}
inline void read(int &x)
{
    x=0; char ch; int flag=1; while (!isdigit(ch=tc())) flag=ch^'-'?1:-1;
    while (x=(x<<3)+(x<<1)+ch-'0',isdigit(ch=tc()));
}
inline void write(int x)
{
    if (x>9) write(x/10);
    putchar(x%10+'0');
}
inline int min(int a,int b)
{
    return a<b?a:b;
}
inline void reset(int x)
{
    register int i; flag[x]=1;
    for (i=(x-1)*size+1;i<=x*size;++i)
    {
        sum[x]-=a[i]; a[i]=sqrt(a[i]); sum[x]+=a[i];
        if (a[i]>1) flag[x]=0;
    }
}
inline int query(int l,int r)
{
    register int i; int res=0;
    for (i=l;i<=min(blk[l]*size,r);++i) res+=a[i];
    if (blk[l]!=blk[r]) for (i=(blk[r]-1)*size+1;i<=r;++i) res+=a[i];
    for (i=blk[l]+1;i<=blk[r]-1;++i) res+=sum[i];
    return res;
}
inline void modify(int l,int r)
{
    register int i;
    for (i=l;i<=min(blk[l]*size,r);++i) 
    sum[blk[l]]-=a[i],a[i]=sqrt(a[i]),sum[blk[l]]+=a[i];
    if (blk[l]!=blk[r]) for (i=(blk[r]-1)*size+1;i<=r;++i) 
    sum[blk[r]]-=a[i],a[i]=sqrt(a[i]),sum[blk[r]]+=a[i];
    for (i=blk[l]+1;i<=blk[r]-1;++i) if (!flag[i]) reset(i);
}
int main()
{
    //freopen("5.in","r",stdin); freopen("5.out","w",stdout);
    register int i; read(n); size=sqrt(n);
    for (i=1;i<=n;++i)
    read(a[i]),sum[blk[i]=(i-1)/size+1]+=a[i];
    for (i=1;i<=n;++i)
    {
        read(opt); read(x); read(y); read(z);
        if (opt) write(query(x,y)),putchar('\n'); else modify(x,y);
    }
    return 0;
}

数列分块入门 6——单点插入,单点询问

首先要正确理解题意,然后我们发现这个和分块有个毛线关系。

我们注意到对于使用数组模拟这个过程时,查询是\(O(1)\)的,但在同时插入时是\(O(n)\)

那么我们思考如何权衡这个问题,我们也可以进行分块。

只不过这里的分块就是对于每块内开一个vector,然后你要知道有一个insert函数真是超级好用

然后我们在插入时可以做到\(O(\sqrt n)\),在数据随机的情况下表现优异。

CODE

#include<cstdio>
#include<cctype>
#include<cmath>
#include<vector>
#define pb push_back
using namespace std;
const int N=200005,BLO=450;
int n,size,opt,x,y,z,tot;
vector <int> r[BLO];
inline char tc(void)
{
    static char fl[100000],*A=fl,*B=fl;
    return A==B&&(B=(A=fl)+fread(fl,1,100000,stdin),A==B)?EOF:*A++;
}
inline void read(int &x)
{
    x=0; char ch; int flag=1; while (!isdigit(ch=tc())) flag=ch^'-'?1:-1;
    while (x=(x<<3)+(x<<1)+ch-'0',isdigit(ch=tc()));
}
inline void write(int x)
{
    if (x>9) write(x/10);
    putchar(x%10+'0');
}
inline int min(int a,int b)
{
    return a<b?a:b;
}
inline int query(int x)
{
    for (register int i=1;i<=tot;++i)
    if (x<=r[i].size()) return r[i][x-1]; else x-=r[i].size();
}
inline void insert(int x,int y)
{
    for (register int i=1;i<=tot;++i)
    if (x<=r[i].size()) { r[i].insert(r[i].begin()+x-1,y); return; } else x-=r[i].size();
}
int main()
{
    //freopen("a.in","r",stdin); freopen("a.out","w",stdout);
    register int i; read(n); size=sqrt(n); tot=(n-1)/size+1;
    for (i=1;i<=n;++i)
    read(x),r[(i-1)/size+1].pb(x);
    for (i=1;i<=n;++i)
    {
        read(opt); read(x); read(y); read(z);
        if (opt) write(query(y)),putchar('\n'); else insert(x,y);
    }
    return 0;
}

不过上面的做法也有一定的缺陷,如果出题人就是要卡你,只需要一直出在同一位置插入的数据即可。

然后最坏情况下就变成\(O(n^2)\)暴力了,然后我们引进重新分块这样的概念

一种重构类似于替罪羊树式重构,当一个块内的元素特别多(可以设临界值)时,直接暴力把这个块裂成两半即可。

但是我更喜欢一个超级粗暴的方式:每做\(\sqrt n\)次操作后,直接重新分块(全部for过去),复杂度和大体的一致,也是\(O(n\sqrt n)\)的。

这样就卡不了你了但是在随机数据下被不重构的分块吊打了

CODE

#include<cstdio>
#include<cctype>
#include<cmath>
#include<vector>
#define pb push_back
using namespace std;
const int N=200005,BLO=450;
int n,size,opt,cur[N],x,y,z,tot,q;
vector <int> r[BLO];
inline char tc(void)
{
    static char fl[100000],*A=fl,*B=fl;
    return A==B&&(B=(A=fl)+fread(fl,1,100000,stdin),A==B)?EOF:*A++;
}
inline void read(int &x)
{
    x=0; char ch; int flag=1; while (!isdigit(ch=tc())) flag=ch^'-'?1:-1;
    while (x=(x<<3)+(x<<1)+ch-'0',isdigit(ch=tc()));
}
inline void write(int x)
{
    if (x>9) write(x/10);
    putchar(x%10+'0');
}
inline int min(int a,int b)
{
    return a<b?a:b;
}
inline void rebuild(void)
{
    register int i,j; n=0;
    for (i=1;i<=tot;++i)
    {
        for (j=0;j<r[i].size();++j)
        cur[++n]=r[i][j]; r[i].clear();
    }
    size=sqrt(n); tot=(n-1)/size+1;
    for (i=1;i<=n;++i)
    r[(i-1)/size+1].pb(cur[i]);
}
inline int query(int x)
{
    for (register int i=1;i<=tot;++i)
    if (x<=r[i].size()) return r[i][x-1]; else x-=r[i].size();
}
inline void insert(int x,int y)
{
    for (register int i=1;i<=tot;++i)
    if (x<=r[i].size()) { r[i].insert(r[i].begin()+x-1,y); return; } else x-=r[i].size();
}
int main()
{
    //freopen("6.in","r",stdin); freopen("6.out","w",stdout);
    register int i; read(n); size=sqrt(n); tot=(n-1)/size+1;
    for (i=1;i<=n;++i)
    read(x),r[(i-1)/size+1].pb(x);
    for (q=n,i=1;i<=q;++i)
    {
        read(opt); read(x); read(y); read(z);
        if (opt) write(query(y)),putchar('\n'); else insert(x,y);
        if (i%size==0) rebuild();
    }
    return 0;
}

猜你喜欢

转载自www.cnblogs.com/cjjsb/p/9387565.html