优秀数据结构1-分块(v1.0)

---恢复内容开始---

v1.0,征求大佬的修改意见

众所周知,欣隆妹妹是个毒瘤。

经常出毒瘤数据结构题。

出的最多的就是分块。

那么,窝们今天就来学习优(暴)秀(力)数据结构--分块

可爱的由乃。

QAQ

part-one 什么是分块?

分块,顾名思义,就是把序列分成相等的若干个块,对于每一个块,维护信息,然后再合并。

part-two 分块的原理,实现?

现在,我们有一个问题,对一个序列,区间加法,求区间每一个数的和(假设我们不会一种东西叫线段树)

暴力做法:

暴力修改,暴力查询,显然T飞,时间复杂度O(n²)。

考虑优化暴力:

把序列分成ω个块,每一个块都维护一个TAG。

l,r区间分为整块部分和零散块部分。

对于整块的部分,我们直接对块的TAG加上x。

对于零散的部分,我们暴力加。

查询也是一样。

对于整块的部分,就是块内原来的和+(n/ω)*TAG>

对于零散的部分,直接暴力查询。

时间复杂度。

整块最多ω个,所以时间复杂度O(ω)。

零散部分最多(n/ω)个,时间复杂度O(n/ω)。

显然ω=sqrt(n)是最优。

所以分块时间复杂度m*sqrt(n).

分块大概实现:

记录bel数组,代表一个点属于哪一个块。

记录lft,rgt数组,代表每一个块的左端点和右端点

然后对与每一个块统计信息。

对于每一次查询

若两个端点在同一个块中,则暴力查询。

否则先查询整块信息,再暴力查询零散部分

对于每一个修改。

整块信息直接打TAG,零散部分暴力修改。

part three-例题(因不同题写题时间可能差异较大,码风可能不同)

1.弹飞绵羊(luogu3203)

题目大意:Lostmonkey在地上沿着一条直线摆上n个装置,每个装置设定初始弹力系数ki,当绵羊达到第i个装置时,它会往后弹ki步,达到第i+ki个装置,若不存在第i+ki个装置,则绵羊被弹飞。绵羊想知道当它从第i个装置起步时,被弹几次后会被弹飞。

基础题,对装置分块,记录每一个点跳几次能跳出当前所在块,跳出后跳到几号节点。

代码:

#include <iostream>
#include <stdio.h> #include <string> #include <math.h> using namespace std; const int maxn=200005,maxm=505; int n,m,Q,num[maxn],bel[maxn],sum[maxn],outt[maxn]; struct block { int l,r; } a[maxn]; inline int read() { int x=0; char ch=getchar(); while (ch<'0'||ch>'9') ch=getchar(); while (ch>='0'&&ch<='9') x=x*10+ch-48,ch=getchar(); return x; } void doit(int l,int r) { for (int i=r; i>=l; i--) if (i+num[i]>a[bel[i]].r) sum[i]=1,outt[i]=i+num[i]; else sum[i]=sum[i+num[i]]+1,outt[i]=outt[i+num[i]]; } int main() { n=read(); m=sqrt(n); if (m*m<n) m++; int s=0; for (int i=1; i<=n; i++) num[i]=read(); for (int i=1; i<=n; i+=m) a[++s].l=i,a[s].r=i+m-1; if (s<m) a[++s].l=s*m+1,a[s].r=n; s=1; for (int i=1; i<=n; i++) { if (i>a[s].r) s++; bel[i]=s; } doit(1,n); Q=read(); while (Q--) { int x=read(),y=read()+1; if (x==1) { int ans=sum[y],x=outt[y]; for (int i=bel[y]; i<=m&&x<=n; i++) ans+=sum[x],x=outt[x]; printf("%d\n",ans); } else { int z=read(); num[y]=z; doit(a[bel[y]].l,a[bel[y]].r); } } return 0; }

2.lucky array(CF121E)

其实这题更简单,本该放在前面。

题目大意:区间加法,区间幸运数个数。

因为值域很小,我们发现在值域中的幸运数个数为30,所以我们先打个表,然后分块。

先统计每一个块内每一个数的出现次数,然后对于加法,我们还是打TAG,只不过在查询的时候要查询=(幸运数字-TAG)的个数

然后就好了。

代码:

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#define Rint register int
#define Temp template<typename T>
#define update(x,y,z) num[x][y]--,num[x][y+=z]++
using namespace std;
Temp inline void read(T &x) {
    x=0;T w=1,ch=getchar();
    while(!isdigit(ch)&&ch!='-') ch=getchar();
    if(ch=='-') w=-1,ch=getchar();
    while(isdigit(ch)) x=x*10+ch-'0',ch=getchar();
    x*=w;
} 
const int t[30]={4,7,44,47,74,77,444,447,474,477,744,747,774,777,4444,4447,4474,4477
,4744,4747,4774,4777,7444,7447,7474,7477,7744,7747,7774,7777};
const int m=317*5;
const int maxn=1e5+10;
int n,q,cnt;
int a[maxn];
int num[105][maxn],id[maxn],low[maxn],high[maxn],add[maxn];
int vis[10010];
int main() {
    read(n);read(q);
    for (int i=1;i<=n;++i) {
        read(a[i]);
        id[i]=i/m+1;
        num[id[i]][a[i]]++;
    }
    cnt=id[n];
    for (int i=1;i<=cnt;++i) {
        low[i]=(i-1)*m;
        high[i]=i*m-1;
    }
    low[1]=1;high[cnt]=n;
    for (int i=0;i<30;++i) vis[t[i]]=1;
    while(q--) {
        char kind[10];
        int l,r,d;
        scanf("%s%d%d",kind,&l,&r);
        int lx=id[l],rx=id[r];
        if(*kind=='a') {
            scanf("%d",&d);
            if(lx==rx) {
                for (int i=l;i<=r;++i) update(lx,a[i],d);
            }
            else {
                for (int i=l;i<=high[lx];++i) update(lx,a[i],d);
                for (int i=lx+1;i<rx;++i) add[i]+=d;
                for (int i=low[rx];i<=r;++i) update(rx,a[i],d);
            }
        }
        else {
            int ans=0;
            if(lx==rx) {
                for (int i=l;i<=r;++i) ans+=vis[a[i]+add[lx]];
            }
            else {
                for (int i=l;i<=high[lx];++i) ans+=vis[a[i]+add[lx]];
                for (int i=lx+1;i<rx;++i) {
                    for (int j=0;j<30;++j) if(add[i]<=t[j]) ans+=num[i][t[j]-add[i]];
                }
                for (int i=low[rx];i<=r;++i) ans+=vis[a[i]+add[rx]];
            }
            printf("%d\n",ans);
        }
    }
    return 0;
}

part four-Ynoi(不提供代码,不然没啥意思)

1.luogu5397 天降之物(第四分块)

题目大意:将所有x变成y,求最小的|i-j|使得a[i]==x && a[j]==y。

定义size[x]为x出现次数.

by lxl(写了一遍题解发现自己写得太垃圾了)

2. 五彩斑斓的世界。(第二分块)

题目大意:把区间>x的数减去x,求区间l,r内x的出现次数,值域1e5

解法:

提示了值域1e5,时间复杂度肯定与值域有关。

这题明显有一个性质,就是所有数的最大值总是单调不增的。

考虑利用这个性质

然后,他是要把l,r内>x的数-x,可一转化为把所有数-x,然后把<0的数+x

当最大值>=x*2时,把(1,x)合并到(x+1,x*2)

否则把(x+1,v)合并到(1,v-x)

这个用并查集维护即可。

所以神仙多多指教啊

---恢复内容结束---

猜你喜欢

转载自www.cnblogs.com/zjjandrew/p/11302466.html