分块入门

· · 个人记录

更??的阅读体验?

前言:

趁着 opj 让刷数据结构的理由赶紧水几道入门的分块题。。。

阅读只需要读者知道分块思想,会用分块维护最基本的东西,比如线段树【1】 模板。

以下 n 一般为序列长度,m 为询问次数,V 为值域,a 为给定的数组。

先送两道水紫让大家开心开心!

我永远喜欢珂朵莉~

注意:此处为暴力草过去做法,正解右转隔壁大学

题意:

给定一个长为 n 的序列,每次把区间 [l,r]x 的倍数除 x 或询问区间和。

4s,1.22 GB。

众所周知:

4s,考虑暴力,顺便带大家领略一下卡常的魅力。

先说几个优化比较大的。

for(int i=1;i<=n;i++) sum+=a[i];

可以写为:

for(int i=1;i<=n;i+=5) sum+=a[i],sum+=a[i+1],sum+=a[i+2],sum+=a[i+3],sum+=a[i+4];

原理大概是让访问的内存变连续了,别小看这个,非常有用,优化也比较大。

int a[1000][20]
//写为:
int a[20][1000]

原理与上类似。

剩下一些卡常基本就是因题而定了。

比如这道题,对于 x=2 的操作,可以用位运算优化。

然后这题就完了。

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

int n,m,op,a[100001];

char buf[1<<15],*p1=buf,*p2=buf;
#define nc() (p1==p2&&(p2=buf+fread(p1=buf,1,1<<15,stdin),p1==p2)?-1:*p1++)
inline int rd()
{
    int x=0;char c=nc();
    for(;!isdigit(c);c=nc());
    for(; isdigit(c);c=nc()) x=(x<<3)+(x<<1)+(c^48);
    return x;
}

inline void write(ll x)
{
    if(x>9) write(x/10);
    putchar(x%10+'0');
}

signed main()
{
    n=rd(),m=rd();
    for(register int i=1;i<=n;i=-(~i)) a[i]=rd();
    for(register int i=1;i<=m;i=-(~i))
    {
        op=rd();
        if(op&1)
        {
            int x=rd(),y=rd(),z=rd();
            if(z==1) continue;
            if(z==2)
            {
                register int i=x;
                for(;i+8<=y;i+=8)
                {
                    a[i]=a[i]&1?a[i]:a[i]>>1;
                    a[i+1]=a[i+1]&1?a[i+1]:a[i+1]>>1;
                    a[i+2]=a[i+2]&1?a[i+2]:a[i+2]>>1;
                    a[i+3]=a[i+3]&1?a[i+3]:a[i+3]>>1;
                    a[i+4]=a[i+4]&1?a[i+4]:a[i+4]>>1;
                    a[i+5]=a[i+5]&1?a[i+5]:a[i+5]>>1;
                    a[i+6]=a[i+6]&1?a[i+6]:a[i+6]>>1;
                    a[i+7]=a[i+7]&1?a[i+7]:a[i+7]>>1;
                }
                for(;i<=y;i=-(~i)) a[i]=a[i]&1?a[i]:a[i]>>1;
            }
            else
            {
                register int i=x;
                for(;i+8<=y;i+=8)
                {
                    a[i]=a[i]%z==0?a[i]/z:a[i];
                    a[i+1]=a[i+1]%z==0?a[i+1]/z:a[i+1];
                    a[i+2]=a[i+2]%z==0?a[i+2]/z:a[i+2];
                    a[i+3]=a[i+3]%z==0?a[i+3]/z:a[i+3];
                    a[i+4]=a[i+4]%z==0?a[i+4]/z:a[i+4];
                    a[i+5]=a[i+5]%z==0?a[i+5]/z:a[i+5];
                    a[i+6]=a[i+6]%z==0?a[i+6]/z:a[i+6];
                    a[i+7]=a[i+7]%z==0?a[i+7]/z:a[i+7];
                }
                for(;i<=y;i=-(~i)) a[i]=a[i]%z==0?a[i]/z:a[i];
            }
        }
        else
        {
            ll sum=0;int x=rd(),y=rd();
            register int i=x;
            for(;i+8<=y;i+=8)
            {
                sum+=a[i];
                sum+=a[i+1];
                sum+=a[i+2];
                sum+=a[i+3];
                sum+=a[i+4];
                sum+=a[i+5];
                sum+=a[i+6];
                sum+=a[i+7];
            }
            for(;i<=y;i=-(~i)) sum+=a[i];
            write(sum),putchar('\n');
        }
    }
    return 0;
}

楼房重建

题意:

n 个数,m 次单点修改,求前缀最大值取值个数。

1s,125 MB。

如果你往分块想,那大概率 10 min 就能切掉了。

考虑用 vector 记录每一块里的前缀最大值。

修改时 O(\sqrt n) 暴力重构当前块的 vector 即可。

查询时,对于每一块,upper_bound 一下即可。

复杂度 O(n\sqrt n \log \sqrt n)

常数极小,如果看不起这个复杂度建议看后面由乃打扑克一题。

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

const int N=1e5+5,T=320;
int n,m,L[N],R[N],bel[N];
double a[N];
vector<double> mx[T];

int main()
{
    ios::sync_with_stdio(false);
    cin.tie(0),cout.tie(0);
    cin>>n>>m;
    for(int i=0;i<=n/T;i++)
    {
        L[i]=max(1,i*T),R[i]=min(n,i*T+T-1);
        for(int j=L[i];j<=R[i];j++) bel[j]=i;
        mx[i].push_back(-1);//先插入一个极小值以免 re
    }
    for(int i=1;i<=n;i++) a[i]=-1;//没有建楼房先赋为极小值
    while(m--)
    {
        int x,y;cin>>x>>y;
        a[x]=1.0*y/x;
        int id=bel[x];
        mx[id].clear();
        mx[id].push_back(a[L[id]]);
        for(int i=L[id]+1;i<=R[id];i++) if(a[i]>*mx[id].rbegin()) mx[id].push_back(a[i]);
        int ans=0;double Mx=0;
        for(int i=0;i<=n/T;i++)
        {
            ans+=mx[i].end()-upper_bound(mx[i].begin(),mx[i].end(),Mx);
            Mx=max(Mx,*mx[i].rbegin());
        }
        cout<<ans<<'\n';
    }
}

你的名字

真心觉得有黑。下面俩题跟这个一比,简直是萌萌题。

细节太多,难实现。

题意:

给定一个长为 n 的序列,每次询问区间 [l,r]k 意义下的最小值。

1s,128MB ~ 256MB。

乘法相关,根号分治。

考虑一个阈值 T,先考虑 k < T

下面考虑 k \geq T 的情况。

枚举 k 的倍数 p,对于每个 p,询问 \min\{ a_i \mid l\leq i \leq r \wedge a_i \geq p \}-i,再对所有 p 的这个最小值取 min,即为 k 的答案。

有个 a_i \geq p 的限制,考虑把所有 p 排序,从大到小枚举 p 的同时,把数组中 \geq p 的值插入。

思考如何维护插入 n 个数,处理 \frac{mV}{T} \approx m\sqrt V 次询问。

询问次数太多,需要做到每次询问 O(1)。不难想到猫树或者 ST 表。

但猫树和 ST 表更新一个值是 O(n) 的。

ST 表显然比猫树好写得多,于是考虑改进 ST 表。

考虑分块,每个块维护前缀后缀 min,然后对所有块的最小值 ST 表。

那么中间一大截整的块就可以用 ST 表 O(1) 维护最小值,不完整的块用前缀后缀 min 维护,每次查询 O(1) 不变。

设块长为 L,每次修改 O(L+\frac{n}{L})

但会发现这无法处理 l,r 在同一个块里的情况,但都在同一个块了,你为什么不暴力呢?

然后还有个致命的问题:如果存下所有 p,空间会炸。

可以从 V \sim 1 枚举 k',每次考虑 k' 对其因子的贡献即可。

卡常技巧

代码细节很多。

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

const int N=3e5+5,M=1e5+5,inf=N;
int n,m,V,a[N],ans[N],L[N],R[N],vis[M],ans2[N];
//L[i]~R[i] 存储一段 k 相同的区间。
//ans2:对于第二种情况 l,r 在同一个块的情况
//注意上述两种情况都是对一整个 k 相同的区间操作的,所以对于单个询问的特殊处理需要单独记一下。
struct node{int l,r,k,id;} q[N];
void ckmin(int &x,int y) {if(y<x) x=y;}

char buf[1<<15],*p1=buf,*p2=buf;
#define nc() (p1==p2&&(p2=buf+fread(p1=buf,1,1<<15,stdin),p1==p2)?-1:*p1++)
inline int rd()
{
    int x=0,f=1;char c=nc();
    for(;!isdigit(c);c=nc()) if(c=='-') f=-1;
    for(; isdigit(c);c=nc()) x=(x<<3)+(x<<1)+(c^48);
    return x*f;
}

namespace solve1//构造 bi=ai%k 的做法
{
    const int T=550;
    int v[N],c[N],mn[N/T+5];
    int query(int l,int r)
    {
        int ans=inf;
        if(l/T==r/T) {for(int i=l;i<=r;i++) ans=min(ans,v[i]);return ans;}
        int L=l/T*T+T-1,R=r/T*T;
        for(int i=l;i<=L;i++) ans=min(ans,v[i]);
        for(int i=R;i<=r;i++) ans=min(ans,v[i]);
        for(int i=l/T+1;i<R/T;i++) ans=min(ans,mn[i]);
        return ans;
    }
    void solve(int k)
    {
        for(int i=0;i<k;i++) c[i]=i;
        for(int i=k;i<=V;i++) c[i]=c[i-k];
        //不取模处理每个数 %k 的值
        for(int i=1;i<=n;i++) v[i]=c[a[i]];
        for(int i=0;i<=n/T;i++) mn[i]=inf;
        for(int i=0;i<=n/T;i++)
        {
            int L=max(1,i*T),R=min(n,i*T+T-1);
            for(int j=L;j<=R;j++) mn[i]=min(mn[i],v[j]);
        }
        for(int i=L[k];i<=R[k];i++) ans[q[i].id]=query(q[i].l,q[i].r);
    }
}

namespace solve2
{
    const int T=550,Lg=10;
    int st[Lg][N/T+5],lmn[N],rmn[N],lg[N/T+5];
    vector<int> v[M];
    void upd(int p,int x)
    {
        //维护 前后缀数组
        //由于是从大到小插,可以直接赋值
        int bel=p/T,L=max(1,bel*T),R=min(n,bel*T+T-1);
        for(int i=L;i<=p;i++) rmn[i]=x;
        for(int i=p;i<=R;i++) lmn[i]=x;
        //维护 st 表
        st[0][bel]=x;
        for(int i=1;i<Lg;i++)
        {
            int L=max(0,bel-(1<<i)+1),R=min(bel,n/T-(1<<i)+1);
            for(int j=L;j<=R;j++) st[i][j]=x;
        }
    }
    int query(int l,int r)
    {
        int L=l/T+1,R=r/T-1,t=lg[R-L+1];
        return min(min(lmn[r],rmn[l]),(L<=R?min(st[t][L],st[t][R-(1<<t)+1]):inf));
    }
    void solve()
    {
        for(int i=1;i<=n;i++) v[a[i]].push_back(i);
        for(int i=2;i<=n/T;i++) lg[i]=lg[i>>1]+1;
        for(int i=1;i<=n;i++) lmn[i]=rmn[i]=inf;
        for(int i=0;i<Lg;i++) for(int j=0;j<=n/T;j++) st[i][j]=inf;
        for(int i=V;i;i--)
        {
            for(int p:v[i]) upd(p,i);
            for(int j=1;j*j<=i;j++)
                if(i%j==0)
                {
                    if(!vis[j]) for(int k=L[j];k<=R[j];k++) ckmin(ans[q[k].id],query(q[k].l,q[k].r)-i);
                    if(j*j!=i&&!vis[i/j]) for(int k=L[i/j];k<=R[i/j];k++) ckmin(ans[q[k].id],query(q[k].l,q[k].r)-i);
                }
        }
        //注意 0 是所有数的倍数,最后还要再做一次
        for(int i=1;i<=m;i++) ckmin(ans[q[i].id],query(q[i].l,q[i].r));
    }
}

int main()
{
    n=rd(),m=rd();
    for(int i=1;i<=n;i++) V=max(V,a[i]=rd());
    for(int i=1;i<=m;i++) q[i]={rd(),rd(),rd(),i};
    sort(q+1,q+1+m,[&](node a,node b){return a.k<b.k;});
    for(int i=1;i<=m;i++)
    {
        auto [l,r,k,id]=q[i];
        ans[id]=ans2[id]=inf;
        if(r-l<=550) for(int j=l;j<=r;j++) ckmin(ans2[id],a[j]%k);
        if(!L[k]) L[k]=i;R[k]=i;
    }
    for(int i=1;i<=V;i++)
    {
        if(!L[i]) {vis[i]=1;continue;}
        //粗略计算一下两种复杂度
        if(n<1ll*(V/i)*(R[i]-L[i]+1)*3) solve1::solve(i),vis[i]=1;
    }
    solve2::solve();
    for(int i=1;i<=m;i++) printf("%d\n",(ans2[i]==inf?ans[i]:ans2[i]));
}

由乃打扑克

喜欢我超强样例还不给下数据吗?

题意:

给定一个序列长为 n,每次求区间第 k 小或者区间加上 x

2s,128 MB。

一眼想到 n\sqrt n \log{\sqrt n}\log{V},没想到能过(

主席树之类的显然不能再维护一个区间加的操作,考虑分块。

先考虑如何分块求区间第 k 小。

然后分块维护区间加就很容易了。

卡常技巧:

不清楚为什么这题还需要开 long long。

至于为什么这么简单调了一晚上,因为 TM 看错题了,二分边界赋小了。

块长取 200 比较优秀。

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

const int N=1e5+5,T=200,inf=1e10;
int n,m,a[N],b[N],add[N/T+5],L[N],R[N],bel[N];
void ckmin(int &x,int y) {if(y<x) x=y;}
void ckmax(int &x,int y) {if(y>x) x=y;}

char buf[1<<15],*p1=buf,*p2=buf;
#define nc() (p1==p2&&(p2=buf+fread(p1=buf,1,1<<15,stdin),p1==p2)?-1:*p1++)
inline int rd()
{
    int x=0,f=1;char c=nc();
    for(;!isdigit(c);c=nc()) if(c=='-') f=-1;
    for(; isdigit(c);c=nc()) x=(x<<3)+(x<<1)+(c^48);
    return x*f;
}

void upd(int l,int r,int k)
{
    int lb=bel[l],rb=bel[r];
    if(lb==rb)
    {
        for(int i=l;i<=r;i++) a[i]+=k;
        for(int i=L[lb];i<=R[lb];i++) b[i]=a[i];
        sort(b+L[lb],b+R[lb]+1);
        return;
    }
    for(int i=l;i<=R[lb];i++) a[i]+=k;
    for(int i=L[lb];i<=R[lb];i++) b[i]=a[i];
    sort(b+L[lb],b+R[lb]+1);
    for(int i=L[rb];i<=r;i++) a[i]+=k;
    for(int i=L[rb];i<=R[rb];i++) b[i]=a[i];
    sort(b+L[rb],b+R[rb]+1);
    for(int i=lb+1;i<rb;i++) add[i]+=k;
}

int qry(int l,int r,int op)
{
    int ans=op?inf:-inf,lb=bel[l],rb=bel[r];
    if(lb==rb) {for(int i=l;i<=r;i++) op?ckmin(ans,a[i]+add[lb]):ckmax(ans,a[i]+add[lb]);return ans;}
    for(int i=l;i<=R[lb];i++) op?ckmin(ans,a[i]+add[lb]):ckmax(ans,a[i]+add[lb]);
    for(int i=L[rb];i<=r;i++) op?ckmin(ans,a[i]+add[rb]):ckmax(ans,a[i]+add[rb]);
    for(int i=lb+1;i<rb;i++) op?ckmin(ans,b[L[i]]+add[i]):ckmax(ans,b[R[i]]+add[i]);
    return ans;
}

int qkth(int l,int r,int k)
{
    if(k>r-l+1) return -1;
    int lb=bel[l],rb=bel[r];
    int vL=qry(l,r,1),vR=qry(l,r,0),ans;
    while(vL<=vR)
    {
        int m=(vL+vR)>>1,c=0;
        if(lb==rb) for(int i=l;i<=r;i++) c+=a[i]+add[lb]<=m;
        else
        {
            for(int i=l;i<=R[lb];i++) c+=a[i]+add[lb]<=m;
            for(int i=L[rb];i<=r;i++) c+=a[i]+add[rb]<=m;
            for(int i=lb+1;i<rb;i++)
            {
                if(b[L[i]]+add[i]>m) continue;//最小值>k
                if(b[R[i]]+add[i]<=m) {c+=R[i]-L[i]+1;continue;}//最大值 <= k
                int ll=L[i],rr=R[i],ans=0;
                while(ll<=rr)
                {
                    int mid=(ll+rr)>>1;
                    if(b[mid]+add[i]<=m) ans=mid-L[i]+1,ll=mid+1;
                    else rr=mid-1;
                }
                c+=ans;
            }
        }
        if(c<k) vL=m+1;
        else vR=m-1,ans=m;
    }
    return ans;
}

signed main()
{
    n=rd(),m=rd();
    for(int i=1;i<=n;i++) a[i]=b[i]=rd();
    for(int i=0;i<=n/T;i++)
    {
        L[i]=max(1ll,i*T),R[i]=min(n,i*T+T-1);
        for(int j=L[i];j<=R[i];j++) bel[j]=i;
        sort(b+L[i],b+1+R[i]);
    }
    while(m--)
    {
        int op=rd(),l=rd(),r=rd(),k=rd();
        if(op==1) printf("%d\n",qkth(l,r,k));
        else upd(l,r,k);
    }
}

初始化

题意:

给定一序列,每次求区间和或给定 x,y,z,然后令序列下标为 y,y+x,y+2x,y+3x,...,y+kx 的值加 z

500ms,128MB。

乘法相关,根号分治。

考虑一个阈值 T,对于 x \geq T 的操作,暴力改复杂度为 \frac{n}{T}

下面考虑 x < T

可以对每一个 x 分开维护。

考虑将序列分为每 x 个数一段,那么只需要维护第一段,就能代表所有段。

![](https://cdn.luogu.com.cn/upload/image_hosting/rp4glbai.png) 故只需要维护一个后缀和,前缀和数组即可。 卡常技巧:用 c++98,由于 $\geq T$ 的修改常数极小,这里取 $T=150$。 ```cpp #include<bits/stdc++.h> using namespace std; const int N=2e5+5,T=150,p=1e9+7; int n,m,a[N],s[N/T+5],L[N/T+5],R[N/T+5],bel[N],ls[T+5][T+5],rs[T+5][T+5],vis[T]; void inc(int &x,int y) {if((x+=y)>=p) x-=p;} char buf[1<<15],*p1=buf,*p2=buf; #define nc() (p1==p2&&(p2=buf+fread(p1=buf,1,1<<15,stdin),p1==p2)?-1:*p1++) inline int rd() { int x=0,f=1;char c=nc(); for(;!isdigit(c);c=nc()) if(c=='-') f=-1; for(; isdigit(c);c=nc()) x=(x<<3)+(x<<1)+(c^48); return x*f; } void add(int x,int y,int z) { if(x>=T) {for(int i=y;i<=n;i+=x) inc(a[i],z),inc(s[bel[i]],z);return;} vis[x]=1; for(int i=1;i<=y;i++) inc(rs[x][i],z);//维护后缀 for(int i=y;i<=x;i++) inc(ls[x][i],z);//维护前缀 } int qsum(int l,int r) { int bl=bel[l],br=bel[r],ans=0; if(bl==br) for(int i=l;i<=r;i++) inc(ans,a[i]); else { for(int i=l;i<=R[bl];i++) inc(ans,a[i]); for(int i=L[br];i<=r;i++) inc(ans,a[i]); for(int i=bl+1;i<br;i++) inc(ans,s[i]); } for(int i=1;i<T;i++) { if(!vis[i]) continue; int pl=(l-1)%i+1,pr=(r-1)%i+1,kl=(l-1)/i,kr=(r-1)/i; if(kl==kr) inc(ans,(ls[i][pr]-ls[i][pl-1]+p)%p); else inc(ans,(1ll*(kr-kl-1)*ls[i][i]+ls[i][pr]+rs[i][pl])%p); } return ans; } int main() { n=rd(),m=rd(); for(int i=1;i<=n;i++) a[i]=rd(); for(int i=0;i<=n/T;i++) { L[i]=max(1,i*T),R[i]=min(n,i*T+T-1); for(int j=L[i];j<=R[i];j++) inc(s[i],a[j]); for(int j=L[i];j<=R[i];j++) bel[j]=i; } for(int i=1;i<=m;i++) { int op=rd(),x=rd(),y=rd(),z; if(op==1) z=rd(),add(x,y,z); else printf("%d\n",qsum(x,y)); } } ``` ## [Yuno loves sqrt technology I](https://www.luogu.com.cn/problem/P5046) 题意: > 给定一个长为 $n$ 的排列,每次询问一段区间的逆序对个数,强制在线。 > > $n\leq 10^5$。 > > 750ms,512MB。 可以发现空间贼大,能开到 $n\sqrt n$,但是时限极小。(又要卡常了 考虑分块,问题转换为如何快速合并三个区间的信息。 答案显然为三个区间里的逆序对数+右边角块对中间块的贡献+中间块和右边角块对左边角块的贡献。 先考虑三个区间里的逆序对数。 - 对于边角块,显然只需要处理出每块的前缀逆序对数和后缀逆序对数即可,用树状数组维护,复杂度 $O(n \log n)$。 - 对于中间若干块,可以预处理 $ans_{i,j}$ 数组表示 $i \sim j$ 块的逆序对数,求法考虑处理出 $s_{i,j}$ 表示后 $i$ 个块中 $j$ 出现了多少次,再求一遍前缀和,即可 $O(\sqrt{n})$ 转移,复杂度 $O(n\sqrt{n})$。 然后发现右边角块对中间块的贡献,和中间块对左边角块的贡献也能用 $s$ 数组 $O(\sqrt n)$ 快速算出。 问题只剩右边角块对左边角块的贡献。 实际上是给你两个序列,求右序列给左序列贡献了多少逆序对。 如果两序列分别有序,这不就是归并排的一个步骤吗? 考虑对每一块进行排序,然后 $O(\sqrt n)$ 扫一遍边角块属于的块,如果一个数在询问区间内,则插入序列,如此保证有序。 对于询问区间在同一块内的情况,只需用前缀逆序对数减一下,再减去 $[L,l-1]$ 对 $[l,r]$ 的逆序对贡献即可,其中 $L$ 为所属块的左区间。 这题非常卡常,小编也是卡了两页半捏。这里取块长为 220 左右跑的最快,然后稍微循环展开一下就过了。 ```cpp #include<bits/stdc++.h> #define re register #define fi first #define se second using namespace std; const int N=1e5+5,T=220; pair<int,int> _a[N]; int n,m,k1,k2,a[N],c[N],la[N],lb[N]; int s[N/T+5][N],L[N],R[N],pre[N/T+5][T+5],suf[N/T+2][T+5],bel[N]; long long ans[N/T+2][N/T+5],lans; inline void upd(int x,int v) {for(;x<=n;x+=x&-x) c[x]+=v;} inline int sum(int x) {int r=0;for(;x;x^=x&-x) r+=c[x];return r;} char buf[1<<15],*p1=buf,*p2=buf; #define nc() (p1==p2&&(p2=buf+fread(p1=buf,1,1<<15,stdin),p1==p2)?-1:*p1++) inline int rd() { int x=0;char c=nc(); for(;!isdigit(c);c=nc()); for(; isdigit(c);c=nc()) x=(x<<3)+(x<<1)+(c^48); return x; } int merge() { int res=0; for(re int i=1,j=1;i<=k1&&j<=k2;) if(la[i]>lb[j]) j++,res+=k1-i+1; else i++; return res; } inline long long query(int l,int r) { int bl=bel[l],br=bel[r]; long long res=0; if(bl==br) { k1=k2=0; for(int j=L[bl];j<=R[bl];j++) if(_a[j].se>=l&&_a[j].se<=r) lb[++k2]=_a[j].fi; else if(_a[j].se<l) la[++k1]=_a[j].fi; return pre[bl][r-L[bl]+1]-pre[bl][l-L[bl]]-merge(); } res=suf[bl][l-L[bl]+1]+pre[br][r-L[br]+1]+ans[bl+1][br-1]; for(re int i=L[br];i<=r;i++) res+=s[bl+1][n]-s[bl+1][a[i]]-(s[br][n]-s[br][a[i]]); for(re int i=l;i<=R[bl];i++) res+=s[bl+1][a[i]-1]-s[br][a[i]-1]; k1=k2=0; for(re int i=L[bl];i<=R[bl];i++) if(_a[i].se>=l) la[++k1]=_a[i].fi; for(re int i=L[br];i<=R[br];i++) if(_a[i].se<=r) lb[++k2]=_a[i].fi; return res+merge(); } int main() { n=rd(),m=rd(); for(re int i=1;i<=n;i++) a[i]=rd(),_a[i]={a[i],i}; for(re int i=0;i<=n/T;i++) { int l=max(1,i*T),r=min(n,i*T+T-1); L[i]=l,R[i]=r; for(re int j=l;j<=r;j++) bel[j]=i,s[i][a[j]]++; for(re int j=l;j<=r;j++) pre[i][j-l+1]=pre[i][j-l]+sum(n)-sum(a[j]),upd(a[j],1); for(re int j=l;j<=r;j++) upd(a[j],-1); for(re int j=r;j>=l;j--) suf[i][j-l+1]=suf[i][j-l+2]+sum(a[j]-1),upd(a[j],1); for(re int j=l;j<=r;j++) upd(a[j],-1); sort(_a+L[i],_a+R[i]+1); } for(re int i=0;i<=n/T;i++) ans[i][i]=suf[i][1]; for(re int i=n/T-1;~i;i--) for(int j=1;j<=n;j+=5)//循环展开 { s[i][j]+=s[i+1][j]; s[i][j+1]+=s[i+1][j+1]; s[i][j+2]+=s[i+1][j+2]; s[i][j+3]+=s[i+1][j+3]; s[i][j+4]+=s[i+1][j+4]; } for(re int i=0;i<=n/T;i++) for(int j=1;j<=n;j+=5)//循环展开 { s[i][j]+=s[i][j-1]; s[i][j+1]+=s[i][j]; s[i][j+2]+=s[i][j+1]; s[i][j+3]+=s[i][j+2]; s[i][j+4]+=s[i][j+3]; } for(re int j=n/T;j;j--) { long long sum=ans[j][j]; for(re int i=j-1;~i;i--) { sum+=ans[i][i]; for(re int k=L[i];k<=R[i];k++) sum+=s[i+1][a[k]-1]-s[j+1][a[k]-1]; ans[i][j]=sum; } } while(m--) { int l=rd()^lans,r=rd()^lans; printf("%lld\n",lans=query(l,r)); } } ``` ## [Yuno loves sqrt technology II(莫队二次离线)](https://www.luogu.com.cn/problem/P5047) 默认读者会莫队。 题意: > 给定一个长为 $n$ 的序列,每次询问一段区间的逆序对个数。 > > $n,m\leq 10^5$。 > > 250ms,31.25MB。 如果您不是用低于 $O(n^{1.5})$ 算法的神仙,这边显然用离线算法了。 定义 $(i,j,k)$ 为 $[i,j]$ 中有多少数比 $k$ 大。 先想想暴力怎么做,就是 $\sum\limits_{l \leq i \leq r} (l,i,i)$。 这个可以差分为:$\sum\limits_{l \leq i \leq r} (1,i,i)-(1,l-1,i)$。 前面一段很好维护,直接树状数组求一遍,然后前缀和即可。 然后后面那个,考虑对于每个 $l$,用 vector 存一下需要查询的区间,当加入了 $a_{1} \sim a_{l-1}$ 时,用一个数据结构维护一下就可以求了。 但存的区间是 $nm$ 级别的,这个时候就需要莫队了。 可以把莫队调整位置的移动储存下来,那么区间就是 $n\sqrt m$ 级别了。 假设上一次处理了 $[l',r']$ 的答案,现在要处理 $[l,r]$,思考一下我们要存下来的区间。 先让答案 $[l',r'] \rightarrow [l',r]$,需要处理 $(1,l-1,r \sim r')$ 。 再让答案 $[l',r ] \rightarrow [l ,r]$,需要处理 $(r+1,n,l \sim l')$ 。(注意这里的定义是小于) 也就是加上莫队后还要考虑 $r$ 存储的区间,所以还需要倒着做一遍。 现在考虑维护 $n \sqrt m$ 询问,$n$ 次插入,每次询问插入的数中有多少比当前的数大。 可以用值域分块维护,顾名思义就是对值域分块,做到询问 $O(1)$,插入 $O(\sqrt n)$。 注意我们处理的是基于上一次答案的变化量,所以输出答案之前还要求一遍前缀和。 然后这题我因为值域分块是块的 id 从 0 开始且没有特判调了 1.5h ... ```cpp #include<bits/stdc++.h> #define ll long long using namespace std; const int N=1e5+5,T=320; int n,m,V,a[N],lsh[N],c[N]; int L[N/T+5],R[N/T+5],bel[N],tag[N/T+5],cnt[N]; ll ans[N],s1[N],s2[N]; struct node{int l,r,w,id;} q[N]; vector<node> q1[N],q2[N]; void upd(int x,int v) {for(;x<=V;x+=x&-x) c[x]+=v;} int qsum(int x) {int r=0;for(;x;x^=x&-x) r+=c[x];return r;} void add1(int x)//给 [1,x] +1 { int id=bel[x]; for(int i=L[id];i<=x;i++) cnt[i]++; for(int i=1;i<id;i++) tag[i]++; } void add2(int x)//给 [x,V] +1 { int id=bel[x]; for(int i=x;i<=R[id];i++) cnt[i]++; for(int i=id+1;i<=V/T+1;i++) tag[i]++; } int ask(int x) {return cnt[x]+tag[bel[x]];} char buf[1<<15],*p1=buf,*p2=buf; #define nc() (p1==p2&&(p2=buf+fread(p1=buf,1,1<<15,stdin),p1==p2)?-1:*p1++) inline int rd() { int x=0;char c=nc(); for(;!isdigit(c);c=nc()); for(; isdigit(c);c=nc()) x=(x<<3)+(x<<1)+(c^48); return x; } int main() { n=rd(),m=rd(); for(int i=1;i<=n;i++) a[i]=lsh[i]=rd(); sort(lsh+1,lsh+1+n); V=unique(lsh+1,lsh+1+n)-lsh-1; for(int i=1;i<=n;i++) a[i]=lower_bound(lsh+1,lsh+1+V,a[i])-lsh; for(int i=1;i<=n;i++) s1[i]=i-1-qsum(a[i])+s1[i-1],upd(a[i],1); memset(c,0,sizeof(c)); for(int i=n;i>=1;i--) s2[i]=qsum(a[i]-1)+s2[i+1],upd(a[i],1); for(int i=1;i<=m;i++) q[i]={rd(),rd(),0,i}; sort(q+1,q+1+m,[&](node a,node b) {return (a.l/T==b.l/T)?a.r<b.r:a.l<b.l;}); int l=1,r=0; for(int i=1;i<=m;i++) { if(r<q[i].r) ans[q[i].id]+=(s1[q[i].r]-s1[r]),q1[l].push_back({r+1,q[i].r,-1,q[i].id}),r=q[i].r; if(r>q[i].r) ans[q[i].id]-=(s1[r]-s1[q[i].r]),q1[l].push_back({q[i].r+1,r, 1,q[i].id}),r=q[i].r; if(l<q[i].l) ans[q[i].id]-=(s2[l]-s2[q[i].l]),q2[r].push_back({l,q[i].l-1, 1,q[i].id}),l=q[i].l; if(l>q[i].l) ans[q[i].id]+=(s2[q[i].l]-s2[l]),q2[r].push_back({q[i].l,l-1,-1,q[i].id}),l=q[i].l; } for(int i=0;i<=V/T;i++) { L[i+1]=max(1,i*T),R[i+1]=min(V,i*T+T-1); for(int j=L[i+1];j<=R[i+1];j++) bel[j]=i+1; } //处理 (1,l-1,x) for(int i=1;i<=n;i++) { for(auto &[l,r,w,id]:q1[i]) for(int j=l;j<=r;j++) ans[id]+=w*ask(a[j]+1); add1(a[i]); } memset(tag,0,sizeof(tag));memset(cnt,0,sizeof(cnt)); for(int i=n;i>=1;i--) { for(auto &[l,r,w,id]:q2[i]) for(int j=l;j<=r;j++) ans[id]+=w*ask(a[j]-1); add2(a[i]); } for(int i=1;i<=m;i++) ans[q[i].id]+=ans[q[i-1].id]; for(int i=1;i<=m;i++) printf("%lld\n",ans[i]); } ``` ## [CF765F Souvenirs](https://www.luogu.com.cn/problem/CF765F) 首先想的是一个回滚莫队+压位 trie 的做法,但感觉不优雅。 考虑分块,感觉处理方式很像在线区间逆序对。 把答案分为三部分: - 一段整块 - 边角块对整块 - 边角块对边角块 可以预处理数组 $mn_{i,j}$ 表示考虑整个序列中 $j$ 这个位置,选块 $i$ 里的数的最小值。 先对原序列排序,然后枚举每一块,块内排序后 $O(n+\sqrt n)$ 双指针即可,这里总复杂度 $O(n\sqrt n)$。 然后对 $ans_{i,j}$ 进行前缀 min,后缀 min,然后就可以 $O(n\sqrt n)$ 预处理出 $ans_{i,j}$ 表示块 $[i,j]$ 之间的答案。边角块对整块的贡献也可以 $O(\sqrt n)$ 计算。 考虑边角块对边角块如何求贡献。 将两个边角块组成的序列分别排序然后双指针即可,但我们不能每次取出来排序,而是先块内排序完,然后从小到大扫这个块,如果当前数在询问范围内,则加到数组后面。 ```cpp #include<bits/stdc++.h> using namespace std; const int N=1e5+5,T=320; int n,q,k1,k2,a[N],b1[N],b2[N],L[T+5],R[T+5],bel[N],mn[T+5],ans[T+5][T+5],pre[T+5][N],suf[T+5][N]; struct node{ int v,id; bool operator<(const node &x)const{ return v<x.v; } }_a[N],b[N]; char buf[1<<15],*p1=buf,*p2=buf; #define nc() (p1==p2&&(p2=buf+fread(p1=buf,1,1<<15,stdin),p1==p2)?-1:*p1++) inline int rd() { int x=0;char c=nc(); for(;!isdigit(c);c=nc()); for(; isdigit(c);c=nc()) x=(x<<3)+(x<<1)+(c^48); return x; } int main() { n=rd(); for(int i=1;i<=n;i++) a[i]=rd(),_a[i]=b[i]={a[i],i}; sort(b+1,b+1+n); memset(pre,0x3f,sizeof(pre)),memset(suf,0x3f,sizeof(suf));memset(ans,0x3f,sizeof(ans)); for(int i=0;i<=n/T;i++) { L[i]=max(1,i*T),R[i]=min(n,i*T+T-1),mn[i]=2e9; sort(_a+L[i],_a+1+R[i]); for(int j=L[i];j<=R[i];j++) bel[j]=i; for(int j=L[i];j< R[i];j++) mn[i]=min(mn[i],_a[j+1].v-_a[j].v); for(int j=1,k=L[i];j<=n;j++) { auto [v,id]=b[j]; while(k+1<=R[i]&&_a[k].v<v) k++; pre[i][id]=suf[i][id]=abs(_a[k].v-v); if(k!=L[i]) pre[i][id]=suf[i][id]=min(pre[i][id],v-_a[k-1].v); } } for(int i=1;i<=n;i++) { int id=bel[i]; for(int j=id-2;j>=0 ;j--) suf[j][i]=min(suf[j][i],suf[j+1][i]); for(int j=id+2;j<=n/T;j++) pre[j][i]=min(pre[j][i],pre[j-1][i]); } for(int i=0;i<=n/T;i++) { int now=mn[i];ans[i][i]=mn[i]; for(int j=i+1;j<=n/T;j++) { now=min(now,mn[j]); for(int k=L[j];k<=R[j];k++) now=min(now,suf[i][k]); ans[i][j]=now; } } q=rd(); while(q--) { int l=rd(),r=rd(); int bl=bel[l],br=bel[r],res=2e9;k1=k2=0; if(bl==br) { for(int i=L[bl];i<=R[bl];i++) if(_a[i].id>=l&&_a[i].id<=r) b1[++k1]=_a[i].v; for(int i=1;i<k1;i++) res=min(res,b1[i+1]-b1[i]); cout<<res<<'\n';continue; } res=ans[bl+1][br-1]; if(br-bl>1) { for(int i=l;i<=R[bl];i++) res=min(res,pre[br-1][i]); for(int i=L[br];i<=r;i++) res=min(res,suf[bl+1][i]); } for(int i=L[bl];i<=R[bl];i++) if(_a[i].id>=l) b1[++k1]=_a[i].v; for(int i=L[br];i<=R[br];i++) if(_a[i].id<=r) b2[++k2]=_a[i].v; for(int i=1;i<k1;i++) res=min(res,b1[i+1]-b1[i]); for(int i=1;i<k2;i++) res=min(res,b2[i+1]-b2[i]); for(int i=1,j=1;i<=k1;i++) { while(j+1<=k2&&b2[j]<b1[i]) j++; res=min(res,abs(b2[j]-b1[i])); if(j!=1) res=min(res,b1[i]-b2[j-1]); } cout<<res<<'\n'; } } ```