Loading

分块入门

有用的参考资料:
hzwer分块九讲
分块九讲提交

分块是一种优美的暴力算法。
其思想为将数组分为若干块,修改、查询时将整块一起处理,而对剩余的元素,即散点进行暴力处理,以优化时间复杂度。
同时,分块的适用性更加广泛,可以解决插入元素,区间众数等线段树所不能解决的操作。

假设数组长度为 \(n\),而分出的块大小为 \(B\),则一共有 \(\left\lfloor\dfrac{n}{B}\right\rfloor\) 个整块。
假设对单点和整块的操作单次时间复杂度是 \(O(1)\),则单次操作时间复杂度为 \(O\left(\left\lfloor\dfrac{n}{B}\right\rfloor+B\right)\),当 \(B=\lfloor\sqrt{n}\rfloor\) 时取得最优时间复杂度 \(O(\sqrt{n})\)
则总时间复杂度为 \(O(m\sqrt{n})\),其中 \(m\) 为询问次数。若 \(n,m\) 同级,则最终时间复杂度为 \(O(n\sqrt{n})\)

如果单点和整块操作的时间复杂度有变化,那么上述分析也会有相应的变化。

接下来,我们通过若干道例题来学习分块的一些操作。

0. 分块的大致实现

在做题之前,我们首先要了解分块的大致实现。
首先要处理出每个下标所在的块的编号。
一般来说,我们希望 \(a_1, a_2, \cdots, a_B\)在一个块,\(a_{B+1}, a_{B+2}, \cdots, a_{2B}\)在一个块,以此类推。
这样的分块可以用下面的代码实现:

for(int i=1;i<=n;i++)bel[i]=(i-1)/B+1;

其中 \(bel\)\(belong\) 的缩写,即这个元素是属于哪个块的。
有时候我们要查询所在块的左右端点,这时我们可以这样操作:

int lp=(pos-1)*B+1,rp=min(pos*B,n);

其中 \(pos\) 是所在块的编号。注意查询的可能是最后的不完整块,这里右指针中的取 \(\min\) 操作保证了不会越界。

有了这些最基础的,我们就能大致写出分块“整块处理,剩余暴力”的代码了。(以查询为例)

int query(int l,int r,...)
{
    int lpos=bel[l],rpos=bel[r];
    int ans=0;//查询操作需要返回答案
    if(lpos==rpos)for(int i=l;i<=r;i++);//询问区间在同一块内直接暴力即可
    else
    {
        for(int i=lpos+1;i<=rpos-1;i++);//整块的一起处理
        for(int i=l;bel[i]==lpos;i++);//剩余的同样暴力即可
        for(int i=r;bel[i]==rpos;i--);
    }
    return ans;
}

1. 基础操作

1.1 数列分块入门1:区间加,单点查询

基础中的基础。
我们给每一个块打上一个加法标记,区间内数字的实际值就是原数组中的值加上对应块的加法标记。
整块修改加到加法标记上,剩余的单点修改加到原数组上就行了。
时间复杂度 \(O(n\sqrt{n})\)

说句闲话:块的大小也不一定要是严格的 \(\sqrt{n}\)
可以定一个常数,并根据实际的运行速度三分调整;也可以依照时间复杂度分析定成其他的形式,根据实际运行速度进行调整。
总之能卡过就行(

代码:

const int maxn=50010,B=220;
int n,a[maxn],bel[maxn],add[500];//add是加法标记
void modify(int l,int r,int k)
{
    int lpos=bel[l],rpos=bel[r];
    if(lpos==rpos)for(int i=l;i<=r;i++)a[i]+=k;//单点修改加到原数组上
    else
    {
        for(int i=lpos+1;i<=rpos-1;i++)add[i]+=k;//整块修改加到加法标记上
        for(int i=l;bel[i]==lpos;i++)a[i]+=k;
        for(int i=r;bel[i]==rpos;i--)a[i]+=k;
    }
}
inline int query(int k){return a[k]+add[bel[k]];}//实际的值是原数组中的值加上对应块的加法标记
int main()
{
    n=read();
    for(int i=1;i<=n;i++)a[i]=read();
    for(int i=1;i<=n;i++)bel[i]=(i-1)/B+1;
    //...
    return 0;
}

1.2 数列分块入门4:区间加,区间求和

要高效地处理区间求和,需要效仿加法标记,引入 \(sum\) 数组来表示块内的所有元素之和。
注意 \(sum\) 数组和 \(add\) 标记是相互独立的,互不影响。
时间复杂度 \(O(n\sqrt{n})\)。详细内容见代码注释。

#define int long long//不开long long见祖宗
const int maxn=50010,B=220;
int n,mod,a[maxn],bel[maxn],add[500],sum[500];
void modify(int l,int r,int k)
{
    int lpos=bel[l],rpos=bel[r];
    if(lpos==rpos)
        for(int i=l;i<=r;i++)
        {
            a[i]+=k;
            sum[lpos]+=k;//修改的时候要记得更新sum数组
        }
    else
    {
        for(int i=lpos+1;i<=rpos-1;i++)
        {
            add[i]+=k;
            sum[i]+=B*k;//需要注意修改整个块对sum的贡献是B*k
        }
        for(int i=l;bel[i]==lpos;i++)
        {
            a[i]+=k;
            sum[lpos]+=k;
        }
        for(int i=r;bel[i]==rpos;i--)
        {
            a[i]+=k;
            sum[rpos]+=k;
        }
    }
}
int query(int l,int r)
{
    int lpos=bel[l],rpos=bel[r],ans=0;
    if(lpos==rpos)
        for(int i=l;i<=r;i++)
        {
            ans+=a[i]+add[lpos];//别忘了加上加法标记
            ans%=mod;
        }
    else
    {
        for(int i=lpos+1;i<=rpos-1;i++)
        {
            ans+=sum[i];//维护sum数组让我们轻松询问每一块中的元素和
            ans%=mod;
        }
        for(int i=l;bel[i]==lpos;i++)
        {
            ans+=a[i]+add[lpos];
            ans%=mod;
        }
        for(int i=r;bel[i]==rpos;i--)
        {
            ans+=a[i]+add[rpos];
            ans%=mod;
        }
    }
    return ans;
}
signed main()
{
    n=read();
    for(int i=1;i<=n;i++)
    {
        a[i]=read();
        bel[i]=(i-1)/B+1;
        sum[bel[i]]+=a[i];//一定要预处理sum数组!!!
    }
    //...
    return 0;
}

1.3 多标记处理

1.3.1 数列分块入门7:区间加,区间乘,单点查询

方法同线段树。标记优先级先乘后加。

判断标记优先级的方法:一个个试(

先加后乘:
\((x+add)\cdot mul+k=\left(x+\left(add+\dfrac{k}{mul}\right)\right)\cdot mul\),损失精度。

先乘后加:
\((x\cdot mul+add)+k=x\cdot mul+(add+k)\)
\((x\cdot mul+add)\cdot k=x\cdot(mul\cdot k)+(add\cdot k)\),完全没有问题。
同时这也说明,在区间加的时候修改标记是要让 \(add\) 加上 \(k\),而在区间乘的时候 \(mul\)\(add\) 都要乘上 \(k\)

时间复杂度 \(O(n\sqrt{n})\)

const int maxn=100010,B=320,mod=10007;
int n,a[maxn],bel[maxn],add[500],mul[500];
void release(int pos)//将块的标记释放掉,这样做的解释在之后
{
    int lp=(pos-1)*B+1,rp=min(pos*B,n);
    for(int i=lp;i<=rp;i++)a[i]=(a[i]*mul[pos]%mod+add[pos])%mod;
    mul[pos]=1;add[pos]=0;
}
void modify_add(int l,int r,int k)
{
    int lpos=bel[l],rpos=bel[r];
    if(lpos==rpos)
    {
        //我们发现多标记的时候单点修改会很尴尬。我们不能因为个别的块而直接改标记,这样其他的块也会受影响。
        //但只要将单点所在的块的标记释放掉,我们就可以尽情在原数组上修改了,是不是很暴力呢(
        release(lpos);
        for(int i=l;i<=r;i++)a[i]=(a[i]+k)%mod;
    }
    else
    {
        for(int i=lpos+1;i<=rpos-1;i++)add[i]=(add[i]+k)%mod;//加法操作更新标记的方式在上面已经进行解释了
        release(lpos);
        for(int i=l;bel[i]==lpos;i++)a[i]=(a[i]+k)%mod;
        release(rpos);
        for(int i=r;bel[i]==rpos;i--)a[i]=(a[i]+k)%mod;
    }
}
void modify_mul(int l,int r,int k)//同上,解释略
{
    int lpos=bel[l],rpos=bel[r];
    if(lpos==rpos)
    {
        release(lpos);
        for(int i=l;i<=r;i++)a[i]=a[i]*k%mod;
    }
    else
    {
        for(int i=lpos+1;i<=rpos-1;i++)
        {
            add[i]=add[i]*k%mod;
            mul[i]=mul[i]*k%mod;
        }
        release(lpos);
        for(int i=l;bel[i]==lpos;i++)a[i]=a[i]*k%mod;
        release(rpos);
        for(int i=r;bel[i]==rpos;i--)a[i]=a[i]*k%mod;
    }
}
int query(int x){return (a[x]*mul[bel[x]]%mod+add[bel[x]])%mod;}//先乘后加
int main()
{
    n=read();
    for(int i=1;i<=n;i++)
    {
        a[i]=read()%mod;
        bel[i]=(i-1)/B+1;
        mul[bel[i]]=1;//注意要预处理好乘法标记
    }
    //...
    return 0;
}

1.3.2 区间加,区间乘,区间求和

【模板】线段树 2

把1.2和1.3.2的代码合起来改改再卡卡常就过了。
注意 \(sum\) 数组相对于加法和乘法标记的独立性。时间复杂度 \(O(n\sqrt{n})\)
据本人实测,分块可以不开O2过掉,我的代码最坏的点跑了 \(994\text{ms}\)
附上卡常后的部分代码(其实也没影响多少阅读性)

#define int long long//不开long long见祖宗
const int maxn=100010,B=220;
int n,m,mod,a[maxn],bel[maxn],add[500],mul[500],sum[500];
inline void release(int pos)
{
    int lp=(pos-1)*B+1,rp=min(pos*B,n);
    for(register int i=lp;i<=rp;++i)a[i]=(a[i]*mul[pos]+add[pos])%mod;//使用long long要少取模
    mul[pos]=1;add[pos]=0;
}
void modify_add(int l,int r,int k)
{
    int lpos=bel[l],rpos=bel[r];
    if(lpos==rpos)
    {
        release(lpos);
        for(register int i=l;i<=r;++i)
        {
            a[i]=(a[i]+k)%mod;
            sum[lpos]=(sum[lpos]+k)%mod;//单点加的时候也要维护sum值
        }
    }
    else
    {
        for(register int i=lpos+1;i<=rpos-1;++i)
        {
            add[i]=(add[i]+k)%mod;
            sum[i]=(sum[i]+B*k)%mod;//这里的贡献同1.2
        }
        release(lpos);
        for(register int i=l;bel[i]==lpos;++i)
        {
            a[i]=(a[i]+k)%mod;
            sum[lpos]=(sum[lpos]+k)%mod;
        }
        release(rpos);
        for(register int i=r;bel[i]==rpos;--i)
        {
            a[i]=(a[i]+k)%mod;
            sum[rpos]=(sum[rpos]+k)%mod;
        }
    }
}
void modify_mul(int l,int r,int k)
{
    int lpos=bel[l],rpos=bel[r];
    if(lpos==rpos)
    {
        release(lpos);
        for(register int i=l;i<=r;++i)
        {
            sum[lpos]=(sum[lpos]+a[i]*(k-1))%mod;//乘法操作的贡献是(k-1)倍的原数
            a[i]=a[i]*k%mod;
        }
    }
    else
    {
        for(register int i=lpos+1;i<=rpos-1;++i)
        {
            add[i]=add[i]*k%mod;
            mul[i]=mul[i]*k%mod;
            sum[i]=sum[i]*k%mod;//显然,块内每个数都扩大到原来的k倍,则和也扩大到原来的k倍
        }
        release(lpos);
        for(register int i=l;bel[i]==lpos;++i)
        {
            sum[lpos]=(sum[lpos]+a[i]*(k-1))%mod;
            a[i]=a[i]*k%mod;
        }
        release(rpos);
        for(register int i=r;bel[i]==rpos;--i)
        {
            sum[rpos]=(sum[rpos]+a[i]*(k-1))%mod;
            a[i]=a[i]*k%mod;
        }
    }
}
int query(int l,int r)
{
    int lpos=bel[l],rpos=bel[r],ans=0;
    if(lpos==rpos)
        for(register int i=l;i<=r;++i)
        {
            ans+=a[i]*mul[lpos]+add[lpos];
            ans%=mod;
        }
    else
    {
        for(register int i=lpos+1;i<=rpos-1;++i)
        {
            ans+=sum[i];
            ans%=mod;
        }
        for(register int i=l;bel[i]==lpos;++i)
        {
            ans+=a[i]*mul[lpos]+add[lpos];
            ans%=mod;
        }
        for(register int i=r;bel[i]==rpos;--i)
        {
            ans+=a[i]*mul[rpos]+add[rpos];
            ans%=mod;
        }
    }
    return ans;
}
signed main()
{
    n=read();m=read();mod=read();
    for(register int i=1;i<=n;++i)
    {
        a[i]=read();
        bel[i]=(i-1)/B+1;
        mul[bel[i]]=1;//mul和sum都需要初始化
        sum[bel[i]]+=a[i];
    }
    //...
    return 0;
}

2. 均摊性质

2.1 数列分块入门5:区间开根,区间求和

经典题目。
一个数开不了几次根号就收敛到 \(0\)\(1\) 了。(据说大概需要 \(O(\log\log n)\) 次)
所以我们可以记录整个块内的元素是否都是 \(0\)\(1\),如果是就跳过不作处理。
时间复杂度 \(O(n\sqrt{n}\log\log n)\)

const int maxn=50010,B=220;
int n,a[maxn],bel[maxn],sum[500];
bool flag[500];//判断块内是否全为0/1
void modify(int l,int r)
{
    int lpos=bel[l],rpos=bel[r];
    if(lpos==rpos)
        for(int i=l;i<=r;i++)
        {
            sum[lpos]-=a[i];
            a[i]=(int)sqrt(a[i]);//散点暴力!
            sum[lpos]+=a[i];
        }
    else
    {
        for(int i=lpos+1;i<=rpos-1;i++)
            if(!flag[i])//如果已经全为0/1就不做任何操作
            {
                int lp=(i-1)*B+1,rp=min(i*B,n);
                bool nowflag=1;
                for(int j=lp;j<=rp;j++)
                {
                    sum[i]-=a[j];
                    a[j]=(int)sqrt(a[j]);
                    sum[i]+=a[j];
                    if(a[j]!=0&&a[j]!=1)nowflag=0;//本轮修改后存在某个数大于1,则不能打标记
                }
                if(nowflag)flag[i]=1;//本轮修改后所有的数都为0/1,打上标记
            }
        for(int i=l;bel[i]==lpos;i++)
        {
            sum[lpos]-=a[i];
            a[i]=(int)sqrt(a[i]);
            sum[lpos]+=a[i];
        }
        for(int i=r;bel[i]==rpos;i--)
        {
            sum[rpos]-=a[i];
            a[i]=(int)sqrt(a[i]);
            sum[rpos]+=a[i];
        }
    }
}
int query(int l,int r)
{
    int lpos=bel[l],rpos=bel[r],ans=0;
    if(lpos==rpos)
        for(int i=l;i<=r;i++)
            ans+=a[i];
    else
    {
        for(int i=lpos+1;i<=rpos-1;i++)ans+=sum[i];
        for(int i=l;bel[i]==lpos;i++)ans+=a[i];
        for(int i=r;bel[i]==rpos;i--)ans+=a[i];
    }
    return ans;
}
signed main()
{
    n=read();
    for(int i=1;i<=n;i++)
    {
        a[i]=read();
        bel[i]=(i-1)/B+1;
        sum[bel[i]]+=a[i];
    }
    //...
    return 0;
}

2.2 数列分块入门8:区间赋值,查询区间内某值的个数

容易看出,大段的区间赋值只会使数列快速变成几大段相同数字的形式。
所以我们可以维护每一个块内的数是否都相同,如果都相同那就可以整块一起处理了。
时间复杂度\(O(能过)\)

const int maxn=100010,B=220;
int n,a[maxn],bel[maxn],cover[500];//cover表示如果该块中所有的数都相同,那么这个数是什么
bool flag[500];//标记该块中所有的数是否都相同
inline void release(int pos)//当块内的修改破坏了块内数都相同的性质,我们需要暴力撤掉标记
{
    int lp=(pos-1)*B+1,rp=min(pos*B,n);
    for(int i=lp;i<=rp;i++)a[i]=cover[pos];
    flag[pos]=0;
}
int query(int l,int r,int k)
{
    int lpos=bel[l],rpos=bel[r],ans=0;
    if(lpos==rpos)
    {
        if(flag[lpos])
        {
            if(cover[lpos]==k)ans+=r-l+1;
            release(lpos);
        }
        else for(int i=l;i<=r;i++)if(a[i]==k)ans++;
        for(int i=l;i<=r;i++)a[i]=k;//注意要先撤标记再把新的修改上
    }
    else
    {
        for(int i=lpos+1;i<=rpos-1;i++)
            if(flag[i])
            {
                if(cover[i]==k)ans+=B;//这里可以看出块内标记对时间复杂度的优化作用
                cover[i]=k;
            }
            else
            {
                int lp=(i-1)*B+1,rp=min(i*B,n);
                for(int j=lp;j<=rp;j++)if(a[j]==k)ans++;
                flag[i]=1;
                cover[i]=k;
            }
        int rp=min(lpos*B,n);
        if(flag[lpos])
        {
            if(cover[lpos]==k)ans+=rp-l+1;
            release(lpos);
        }
        else for(int i=l;bel[i]==lpos;i++)if(a[i]==k)ans++;
        for(int i=l;bel[i]==lpos;i++)a[i]=k;
        int lp=(rpos-1)*B+1;
        if(flag[rpos])
        {
            if(cover[rpos]==k)ans+=r-lp+1;
            release(rpos);
        }
        else for(int i=r;bel[i]==rpos;i--)if(a[i]==k)ans++;
        for(int i=r;bel[i]==rpos;i--)a[i]=k;
    }
    return ans;
}
int main()
{
    n=read();
    for(int i=1;i<=n;i++)
    {
        a[i]=read();
        bel[i]=(i-1)/B+1;
    }
    //...
    return 0;
}

3. 分块+二分

其实分块和二分并不兼容,强行二分会让复杂度多一个log。
更强的做法是值域分块,具体见Ynoi最初分块,但是我并不会qaq

3.1 数列分块入门2:区间加法,查询区间内小于某值的个数

考虑将每一个块进行排序,以便于二分。
但是这样我们还需要记录一下原来数组的下标,因为排序后元素的相对顺序发生了变化。当然,是只有块内的相对顺序发生了变化,块外的相对顺序还是相同的。
对于区间加法,整个块内依然有序,但是左右的散点所在块可能会发生变化,因此我们需要再次进行排序。
对于询问,需要对每个整块进行二分查找;散点暴力统计即可。
时间复杂度 \(O(n\sqrt{n}\log n)\)

#define int long long
const int maxn=50010,B=220;
struct node{int val,pos,bel;}a[maxn];
bool cmp(node x,node y){return x.val<y.val;}
int n,cnt,add[500];
void modify(int l,int r,int k)
{
    int lpos=a[l].bel,rpos=a[r].bel;
    if(lpos==rpos)
    {
        int lp=(lpos-1)*B+1,rp=min(lpos*B,n);
        for(int i=lp;i<=rp;i++)//注意散点在现在的坐标上不一定在[l,r]的范围内,需要对整个块进行查找
            if(a[i].pos>=l&&a[i].pos<=r)
                a[i].val+=k;
        sort(a+lp,a+rp+1,cmp);//重新排序
    }
    else
    {
        for(int i=lpos+1;i<=rpos-1;i++)add[i]+=k;//加法标记还是要有的
        int lp=(lpos-1)*B+1,rp=min(lpos*B,n);
        for(int i=lp;i<=rp;i++)if(a[i].pos>=l)a[i].val+=k;//散点,同上
        sort(a+lp,a+rp+1,cmp);
        lp=(rpos-1)*B+1;rp=min(rpos*B,n);
        for(int i=lp;i<=rp;i++)if(a[i].pos<=r)a[i].val+=k;
        sort(a+lp,a+rp+1,cmp);
    }
}
int query(int l,int r,int k)
{
    int ans=0;
    int lpos=a[l].bel,rpos=a[r].bel;
    if(lpos==rpos)
    {
        int lp=(lpos-1)*B+1,rp=min(lpos*B,n);
        for(int i=lp;i<=rp;i++)
            if(a[i].pos>=l&&a[i].pos<=r&&a[i].val+add[lpos]<k)//统计的时候别忘了加上加法标记
                ans++;
    }
    else
    {
        for(int i=lpos+1;i<=rpos-1;i++)
        {
            int lp=(i-1)*B+1,rp=min(i*B,n);
            int ll=lp,rr=rp,now=0;
            while(ll<=rr)//对于整个块进行二分,找到比k小的最大元素位置(不怎么会用lower_bound
            {
                int mid=(ll+rr)>>1;
                if(a[mid].val+add[i]<k)
                {
                    now=mid;
                    ll=mid+1;
                }
                else rr=mid-1;
            }
            if(now)ans+=now-lp+1;//如果存在比k小的元素,则它们都在[lp,now]区间内且有序,于是答案显然为此
        }
        int lp=(lpos-1)*B+1,rp=min(lpos*B,n);
        for(int i=lp;i<=rp;i++)
            if(a[i].pos>=l&&a[i].val+add[lpos]<k)
                ans++;
        lp=(rpos-1)*B+1;rp=min(rpos*B,n);
        for(int i=lp;i<=rp;i++)
            if(a[i].pos<=r&&a[i].val+add[rpos]<k)
                ans++;
    }
    return ans;
}
signed main()
{
    n=read();
    for(int i=1;i<=n;i++)
    {
        a[i].val=read();
        a[i].pos=i;
        a[i].bel=(i-1)/B+1;
    }
    cnt=(int)ceil(n*1.0/B);//其实直接用a[n].bel就行(
    for(int i=1;i<=cnt;i++)//对每个块进行排序
    {
        int lp=(i-1)*B+1,rp=min(i*B,n);
        sort(a+lp,a+rp+1,cmp);
    }
    //...
    return 0;
}

3.2 数列分块入门3:区间加法,查询前驱

和上面一道题差不多,改一改询问的处理就行了。
时间复杂度 \(O(n\sqrt{n}\log n)\)

const int maxn=100010,B=320;
struct node{int val,pos,bel;}a[maxn];
bool cmp(node x,node y){return x.val<y.val;}
int n,cnt,add[500];
void modify(int l,int r,int k)//modify函数同上一题
{
    int lpos=a[l].bel,rpos=a[r].bel;
    if(lpos==rpos)
    {
        int lp=(lpos-1)*B+1,rp=min(lpos*B,n);
        for(int i=lp;i<=rp;i++)
            if(a[i].pos>=l&&a[i].pos<=r)
                a[i].val+=k;
        sort(a+lp,a+rp+1,cmp);
    }
    else
    {
        for(int i=lpos+1;i<=rpos-1;i++)add[i]+=k;
        int lp=(lpos-1)*B+1,rp=min(lpos*B,n);
        for(int i=lp;i<=rp;i++)if(a[i].pos>=l)a[i].val+=k;
        sort(a+lp,a+rp+1,cmp);
        lp=(rpos-1)*B+1;rp=min(rpos*B,n);
        for(int i=lp;i<=rp;i++)if(a[i].pos<=r)a[i].val+=k;
        sort(a+lp,a+rp+1,cmp);
    }
}
int query(int l,int r,int k)
{
    int ans=-1;//题目要求不存在时返回-1
    int lpos=a[l].bel,rpos=a[r].bel;
    if(lpos==rpos)
    {
        int lp=(lpos-1)*B+1,rp=min(lpos*B,n);
        for(int i=lp;i<=rp;i++)
            if(a[i].pos>=l&&a[i].pos<=r&&a[i].val+add[lpos]<k)
                ans=max(ans,a[i].val+add[lpos]);//因为是求前驱,即比k小的最大元素,所以要取max
    }
    else
    {
        for(int i=lpos+1;i<=rpos-1;i++)
        {
            int lp=(i-1)*B+1,rp=min(i*B,n);
            int ll=lp,rr=rp,now=0;
            while(ll<=rr)
            {
                int mid=(ll+rr)>>1;
                if(a[mid].val+add[i]<k)
                {
                    now=mid;
                    ll=mid+1;
                }
                else rr=mid-1;
            }
            if(now)ans=max(ans,a[now].val+add[i]);//同样取max,别忘了加法标记
        }
        int lp=(lpos-1)*B+1,rp=min(lpos*B,n);
        for(int i=lp;i<=rp;i++)
            if(a[i].pos>=l&&a[i].val+add[lpos]<k)
                ans=max(ans,a[i].val+add[lpos]);
        lp=(rpos-1)*B+1;rp=min(rpos*B,n);
        for(int i=lp;i<=rp;i++)
            if(a[i].pos<=r&&a[i].val+add[rpos]<k)
                ans=max(ans,a[i].val+add[rpos]);
    }
    return ans;
}
int main()
{
    n=read();
    for(int i=1;i<=n;i++)
    {
        a[i].val=read();
        a[i].pos=i;
        a[i].bel=(i-1)/B+1;
    }
    cnt=(int)ceil(n*1.0/B);
    for(int i=1;i<=cnt;i++)
    {
        int lp=(i-1)*B+1,rp=min(i*B,n);
        sort(a+lp,a+rp+1,cmp);
    }
    //...
    return 0;
}

咕咕咕

3. 单点插入的处理

3.1 对询问分块

3.2 块状链表

4. 在线区间众数问题

4.1 无修改区间众数

4.2 带修改区间众数

5. 树分块

posted @ 2021-01-03 22:39  pjykk  阅读(358)  评论(0编辑  收藏  举报