分块进阶

广告:分块入门

2023.11.4:补充第四分块代码。

2023.12.26:第十四分块。

2024.1.3:第十分块。

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

听说是大分块中最简单的,于是来做做。

晚自习从 7:00 想到了 9:00,由于明天要打模拟赛,没时间写了,先口糊一下(然后糊都糊了 1h?)。

题意:

给定长为 \(n\) 的序列和 \(m\) 次操作。

操作 1:给定 \(l,r,x\),把区间 \([l,r]\) 所有大于等于 \(x\) 的数减去 \(x\)

操作 2:给定 \(l,r,x\),查询 \([l,r]\) 内值为 \(x\) 的数的个数。

\(n\leq 10^6\)\(m \leq 5\times 10^5\)\(V \leq 10^5+1\)

7.5s,64MB。

Part 1

先考虑操作 1。

只有减操作,先势能分析一下。

手玩一下分析性质。

设当前最大值为 \(mx\)

如果 \(mx \geq 2x\),那么操作后必然 \(mx=mx-x\)

否则对于 \(mx < 2x\),操作后最坏情况 \(mx=x\)

然后不难想到对两种情况如此维护:

  • 对于 \(mx \geq 2x\),枚举值域 \([0,x]\),把贡献合并到 \([0+x,x+x]\),并打上区间 \(-x\) 的标记。
  • 对于 \(mx < 2x\),枚举值域 \((x,mx]\),把贡献合并到 \((x-x,mx-x]\)

显然枚举和值域的减少是同阶的。

由于还有操作 2 的区间查询,所以考虑分块(都是大分块系列了,难道不先考虑分块?)。

那么每一块值域为 \(V\),总值域 \(O(V\sqrt n)\)

怎么维护呢?

Part 2

由于算法标签上有并查集所以嘛。

确实可以用并查集。

对于每一块,考虑维护值 \(i\) 的并查集,出现次数就是当前并查集的 size。合并贡献平凡。

对于边角块,就要暴力重构。

为了保证暴力重构的复杂度,可以用形如 "动态开点并查集" 的方式维护。

大概是维护这么几个东西:

\(rt_i\):值 \(i\) 的根。

\(sz_i\):值 \(i\) 的大小。

\(val_i\):根为 \(i\) 的值。

\(fa_i\)\(i\) 位置的 \(fa\)

Part 3

目前已经可以过弱化版 Welcome home, Chtholly

由于此题空间限制为 64MB ...,显然不能每一块分别开一个并查集。

然后我就不会惹。。。

看了下题解。

原来还可以利用这题不强制在线。

把询问离线,对于每一块分别做一次,分别算贡献即可。

卡常也不难。还是老朋友 c++98 register 和 inline。

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

const int N=1e6+5,M=5e5+5,T=1000;
int n,m,V,L,R,a[N];
int mx,tag,fa[N],rt[N],sz[N],val[N],ans[M];
struct node{int op,l,r,x;} q[M];
int find(int x) {return x==fa[x]?x:fa[x]=find(fa[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;
}

inline void merge(int x,int y)
{
    if(!rt[y]) val[rt[y]=rt[x]]=y;
    else fa[rt[x]]=rt[y];
    sz[y]+=sz[x];
    sz[x]=rt[x]=0;
}

inline void clear(int l=L,int r=R)
{
    for(re int i=l;i<=r;i++)
    {
        a[i]=val[find(i)];
        sz[a[i]]=rt[a[i]]=0;
        a[i]-=tag;
    }
    tag=0;
    for(re int i=l;i<=r;i++) fa[i]=0;
}

inline void build(int l=L,int r=R)
{
    mx=0;
    for(re int i=l;i<=r;i++)
    {
        if(!rt[a[i]]) rt[a[i]]=i,fa[i]=i,val[i]=a[i];
        else fa[i]=rt[a[i]];
        sz[a[i]]++;
        mx=max(mx,a[i]);
    }
}

inline void rebuild(int l,int r,int x)
{
    clear();
    for(re int i=l;i<=r;i++) if(a[i]>x) a[i]-=x;
    build();
}

inline void modify(int x)
{
    if(mx-tag<2*x) {for(re int i=tag+x+1;i<=mx;i++) if(rt[i]) merge(i,i-x);mx=min(mx,tag+x);}
    else {for(re int i=tag+x;i>=tag;i--) if(rt[i]) merge(i,i+x);tag+=x;}
}

int main()
{
    n=rd(),m=rd();
    for(re int i=1;i<=n;i++) V=max(V,a[i]=rd());
    for(re int i=1;i<=m;i++) q[i].op=rd(),q[i].l=rd(),q[i].r=rd(),q[i].x=rd();
    for(re int i=0;i<=n/T;i++)
    {
        L=max(1,i*T),R=min(n,i*T+T-1);
        build();
        for(re int j=1;j<=m;j++)
        {
            int op=q[j].op,l=q[j].l,r=q[j].r,x=q[j].x;
            if(R<l||r<L) continue;
            if(l<=L&&R<=r)
            {
                if(op==1) modify(x);
                else if(x+tag<=V) ans[j]+=sz[x+tag];
            }
            else
            {
                l=max(l,L),r=min(r,R);
                if(op==1) rebuild(l,r,x);
                else for(int k=l;k<=r;k++) if(tag+x==val[find(k)]) ans[j]++;
            }
        }
        clear();
    }
    for(re int i=1;i<=m;i++) if(q[i].op==2) cout<<ans[i]<<'\n';
}

天降之物(第四分块)

难度为 6,想了将近 4 h,一个地方卡了很久。

是根号分治做法。

题意:

给定长为 \(n\) 的序列和 \(m\) 次操作。

操作 1:给定 \(x,y\),把序列中所有值等于 \(x\) 的数变成 \(y\)

操作 2:给定 \(x,y\),找到一个位置 \(i\),使得 \(a_i=x\),找到一个位置 \(j\) 使得 \(a_i=y\),并使 \(|i-j|\) 最小。

\(n,m,V \leq 10^5\)

500ms,256MB。

Part 1

根据我的直觉,空间会开 \(O(n\sqrt n)\) 这么大。

对于操作 2,考虑最暴力的做法,对于每一个值,维护一个位置数组,然后双指针扫一遍 \(x,y\) 的位置数组。

感觉是能优化这个算法的,因为这些数组最多储存 \(n\) 个位置,而对于操作 1,是类似合并两个数组的操作。

考虑根号分治。

对于位置数组大小小于等于 \(\sqrt n\),称为小的,对于位置数组大小大于 \(\sqrt n\) 的,称为大的。

显然大的不会超过 \(O(\sqrt n)\) 个,根据空间直觉,对于大的,处理一个答案数组 \(ans_{i,j}\) 表示值 \(i\) 和值 \(j\) 之间的答案。

这个答案数组直接 \(O(n)\) 预处理即可。

就是预处理这个数组卡了很久,差不多 2.5 h,真是太没实力了。

之前想的是 \(ans_{i,j}\) 表示值 \(i\),位置为 \(j\) 的答案。

但是无法处理两个大的之间的询问。

后面是学校组织”劳改“活动休息了两天回来,突然灵光一现。(扯远了)

Part 2

先考虑操作 2。

  • 小的对小的,直接暴力。

  • 小的对大的,大的对大的,用 \(ans\) 数组。

然后考虑修改。

先遍历 \(ans\) 数组更新,复杂度 \(O(\sqrt n)\)

  • 小的和小的合并,直接暴力,如果大小大于 \(\sqrt n\),就变成大的,处理 \(ans\) 数组。

  • 大的和大的合并,直接暴力重构,更新 \(ans\) 数组。

  • 小的和大的合并,就有问题了,显然不能更新 \(ans\) 数组,那怎么办?

发现直接合并太浪费了,可以先把小的挂在那里,然后等它变大,直到大小大于 \(\sqrt n\),再和大的合并。

所以还要额外维护一个挂着的位置数组,然后操作 2 时也要考虑挂着的数组的影响。

  • 大的和小的合并,与上面等价,为方便,我们可以 swap 一下,但需要记录一个数组 \(fa_i\),表示值 \(i\) 的位置数组下标(编号)为 \(fa_i\),特别的,如果 \(fa_i=0\),表示值 \(i\) 未出现过。

剩下就没什么难的了。

根号分治做法不卡常,随便搞。

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

const int N=1e5+5,T=500;
int n,m,tot,len,lans,a[N],fa[N],id[N],tmp[N],ans[N/T][N];
vector<int> pos[N];
inline void ckmin(int &x,int y) {if(x>y) 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;char c=nc();
    for(;!isdigit(c);c=nc());
    for(; isdigit(c);c=nc()) x=(x<<3)+(x<<1)+(c^48);
    return x;
}

void build(int i)
{
    int &x=id[i];if(!x) x=++tot;
    memset(ans[x],0x3f,sizeof(ans[x]));
    ans[x][i]=0;
    len=1e9;
    for(int j=1;j<=n;j++)
        if(a[j]==i) len=0;
        else ckmin(ans[x][a[j]],++len);
    len=1e9;
    for(int j=n;j>=1;j--)
        if(a[j]==i) len=0;
        else ckmin(ans[x][a[j]],++len);
    pos[i].clear(),pos[i].shrink_to_fit();//注意释放空间
}

int query(int &x,int &y)
{
	if(!x||!y) return 1e9;
    if(x==y) return 0;
	int res=1e9,n=pos[x].size(),m=pos[y].size();
    for(int i=0,j=-1;i<m;i++)
    {
        while(j+1<n&&pos[x][j+1]<pos[y][i]) j++;
        if(~j) ckmin(res,pos[y][i]-pos[x][j]);
        if(j+1!=n) ckmin(res,pos[x][j+1]-pos[y][i]);
        if(res==1) return 1;
    }
	if(id[x]) ckmin(res,ans[id[x]][y]);
	if(id[y]) ckmin(res,ans[id[y]][x]);
	return res;
}

void modify(int &x,int &y)
{
    //x -> y
    if(x==y||!x) return;
	if(!y) return y=x,x=0,void();
	if(id[x]) swap(x,y);
	for(int i=1;i<=tot;i++) ckmin(ans[i][y],ans[i][x]);
	if(id[x]) {for(int i=1;i<=n;i++) if(a[i]==x) a[i]=y;build(y);}
    else
    {
		for(int i:pos[x]) a[i]=y;
        vector<int> tmp;
        int i,j,n=pos[x].size(),m=pos[y].size();
        for(i=0,j=-1;i<m;i++)
        {
            while(j+1<n&&pos[x][j+1]<pos[y][i]) tmp.push_back(pos[x][++j]);
            tmp.push_back(pos[y][i]);
        }
        while(j+1<n) tmp.push_back(pos[x][++j]);
        pos[y]=tmp;
        if(pos[y].size()>T) build(y);
	}
    pos[x].clear(),pos[x].shrink_to_fit();x=0;
}

int main()
{
    memset(ans,0x3f,sizeof(ans));
    n=rd(),m=rd();
    for(int i=1;i<=n;i++) a[i]=rd(),fa[a[i]]=a[i],pos[a[i]].push_back(i);
    for(int i=1;i<=1e5;i++) if(pos[i].size()>T) build(i);
    while(m--)
    {
        int op=rd(),x=rd()^lans,y=rd()^lans;
        if(op==1) modify(fa[x],fa[y]);
        else (lans=query(fa[x],fa[y]))>n?(puts("Ikaros"),lans=0):printf("%d\n",lans);
    }
}

GOSICK(第十四分块)

维包子可爱捏,卡常的出题人一点也不可爱捏。

个人感觉只要会莫队二离,思维难度可能是大分块里最低的,然后就是丧心病狂的卡常。

注意到莫队二次离线需要做到 \(O(\sqrt n)\) 插入,\(O(1)\) 查询,此题的瓶颈就是这个。

先简单提一下怎么莫队二离。

\(f([l,r],i)\) 表示区间 \([l,r]\) 内, \(i\) 的因数和倍数个数。

那么区间 \([l,r]\) 的答案为 \((r-l+1)+\sum\limits_{l\leq i\leq r} f([1,i-1],i)\)

差分为 \(f([1,i-1],i)-f([1,l-1],i)\)

前一部分显然可以预处理,用一个桶维护,插入一个数 \(x\) 时需要枚举 \(x\) 的因数和倍数。

后一部分可以离线 + 扫描线。然后利用莫队把询问数优化到 \(O(n\sqrt n)\)

瓶颈在于插入 \(x\) 时枚举 \(x\) 的因数和倍数。

考虑根号分治,设一个值域 \(B\),对于 \(<B\) 的数,记为 \(x\),对于 \(\geq B\) 的数,记为 \(y\)

那么有以下几种贡献:

  • \(x\leftarrow x\)
  • \(y\leftarrow x\)
  • \(y\leftarrow y\)

对于前面两种贡献,可以 \(O(B)\) 枚举当前的因数 \(j\),然后前缀和维护一下区间 \(j\) 的倍数个数 \(s\),区间 \(j\) 的出现次数 \(c\),那么总贡献点对为 \(s\times c\)

考虑最后一种贡献,对于 \(>B\) 的数,枚举倍数是 \(O(\frac{n}{B})\) 的,于是就做完了。

下面说卡常:

  • 根据多次提交经验,\(B=120\) 比较优秀(因为只有这个过了
  • 莫队块长取 \(1000\) 比较优秀
  • 莫队奇偶排序优化
  • 扫描线的询问用数组存,然后排序
  • c++98 and register
  • 用 vector 存一个数的因数
  • 尽量避免取模
  • 人品
#include<bits/stdc++.h>
#define re register
#define ll long long
using namespace std;

const int N=5e5+5,B=120,L=1000;
ll ans[N],ans2[N],s[N];
int n,m,k,V,a[N],c[N],t[N],num[N],pd[N];
struct node{
    int l,r,id,bl;
    bool operator<(const node&b)const{
        return (bl^b.bl)?(bl<b.bl):((bl&1)?r<b.r:r>b.r);
    }
}q[N];
struct query{
    int p,l,r,id;
    bool operator<(const query&b)const{
        return p<b.p;
    }    
}v[N*2];
vector<int> p[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(),m=rd();
    for(re int i=1;i<=n;i++)
    {
        int x=rd();a[i]=x;V=max(V,x);
        num[i]=num[i-1]+(x>=B);
        if(x>=B&&p[x].empty())
        {
            for(int i=1;i*i<=x;i++)
                if(x%i==0)
                {
                    p[x].push_back(i);
                    if(i!=x/i) p[x].push_back(x/i);
                }
        }
    }
    for(re int i=1;i<=m;i++)
    {
        int l=rd(),r=rd();
        q[i]={l,r,i,l/L},ans2[i]=num[r]-num[l-1];
    }
    for(re int j=1;j<B;j++)
    {
        for(re int i=j;i<=V;i+=j) pd[i]=1;
        for(re int i=1;i<=n;i++) c[i]=c[i-1]+(a[i]==j),s[i]=s[i-1]+pd[a[i]];
        for(re int i=j;i<=V;i+=j) pd[i]=0;
        for(re int i=1;i<=m;i++) ans2[i]+=(s[q[i].r]-s[q[i].l-1])*(c[q[i].r]-c[q[i].l-1]);
    }
    for(re int i=1;i<=n;i++)
    {
        int x=a[i];
        if(x<B) {s[i]=s[i-1];continue;}
        s[i]=s[i-1]+t[x];
        for(re int j=x;j<=V;j+=x) t[j]++;
        for(re int j=0;j<p[x].size();j++) t[p[x][j]]++;
    }
    sort(q+1,q+1+m);
    for(re int i=1,l=1,r=0;i<=m;i++)
    {
        if(l>q[i].l) ans[q[i].id]-=s[l-1]-s[q[i].l-1],v[++k]={r,q[i].l,l-1,q[i].id},l=q[i].l;
        if(r<q[i].r) ans[q[i].id]+=s[q[i].r]-s[r],v[++k]={l-1,r+1,q[i].r,-q[i].id},r=q[i].r;
        if(l<q[i].l) ans[q[i].id]+=s[q[i].l-1]-s[l-1],v[++k]={r,l,q[i].l-1,-q[i].id},l=q[i].l;
        if(r>q[i].r) ans[q[i].id]-=s[r]-s[q[i].r],v[++k]={l-1,q[i].r+1,r,q[i].id},r=q[i].r;
    }
    memset(t,0,sizeof(t));
    sort(v+1,v+1+k);
    int j=1;while(j<=k&&!v[j].p) j++;
    for(re int i=1;i<=n;i++)
    {
        int x=a[i];
        if(x>=B)
        {
            for(re int j=x;j<=V;j+=x) t[j]++;
            for(re int j=0;j<p[x].size();j++) t[p[x][j]]++;
        }
        while(j<=k&&v[j].p==i)
        {
            int l=v[j].l,r=v[j].r,id=v[j].id;
            for(re int j=l;j<=r;j++) if(a[j]>=B) id>0?ans[id]+=(t[a[j]]-(j<=i)-(j<=i)):ans[-id]-=(t[a[j]]-(j<=i)-(j<=i));
            //当 j<=i 时说明插入了 a[j],会贡献两个点对:(j,j),(j,j),需要减去。
            j++;
        }
    }
    for(re int i=1;i<=m;i++) ans[q[i].id]+=ans[q[i-1].id];
    for(re int i=1;i<=m;i++) cout<<ans[i]+ans2[i]<<'\n';
}

魔法少女网站(第十分块)

先考虑不带修。

把询问按 \(x\) 从小到大排序,那么原序列可以抽象为一个 01 序列 \(b\),若 \(b_i=1\) 表示 \(a_i\leq x\)

只需要维护把某些位置变为 \(1\),区间极长 1 序列长度平方之和。随便用一个数据结构维护即可。

对于修改,考虑对询问分块。

把修改按时间排序,每次处理一个询问时,需要支持加入/撤销一些修改。

那么我们需要一个支持 \(O(1)\) 把 0 变成 1,撤销,\(O(\sqrt n)\) 处理询问的数据结构。

序列分块即可。维护一个块内极长连续段的开头,结尾位置。\(O(1)\) 插入只需要合并两个段,修改整块的答案即可。

但是修改操作有可能把 1 -> 0,我们对有修改操作的位置要单独拿出来,看当前是否可以把 0 -> 1。

这个做法常数好像有点亿点大,我现在还没卡过去。。。

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

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;
}

const int N=3e5+5,B=450,B2=2200;
ll s[N],ans[N];
int n,m,k1,k2,b[N],_b[N],c[N],p[N];
int op[N],x[N],y[N],v[N];
int a[N],l[N],r[N],L[N],R[N],bel[N];
int _l[N],_r[N],_s[N];
vector<int> pl,pr,ps;
struct node1{
    int x,v,id;
    bool operator<(const node1 &t)const{
        return id<t.id;
    }
}q1[N];
struct node2{
    int l,r,x,id;
    bool operator<(const node2 &t)const{
        return x<t.x;
    }
}q2[N];
inline ll F(int x) {return 1ll*x*(x+1)>>1;}

void upd(int x,int op)
{
    if(a[x]) return;a[x]=1;
    int id=bel[x],tl=x,tr=x;
    if(op&&_s[id]==-1) _s[id]=s[id],ps.push_back(id);
    if(a[x-1]&&x!=L[id]) tl=l[x-1],s[id]-=F(x-l[x-1]);
    if(a[x+1]&&x!=R[id]) tr=r[x+1],s[id]-=F(r[x+1]-x);
    if(op)
    {
        if(_l[x]==-1) _l[x]=l[x],pl.push_back(x);
        if(_r[x]==-1) _r[x]=r[x],pr.push_back(x);
        if(_l[tr]==-1) _l[tr]=l[tr],pl.push_back(tr);
        if(_r[tl]==-1) _r[tl]=r[tl],pr.push_back(tl);
    }
    l[x]=tl,r[x]=tr,r[tl]=tr,l[tr]=tl,s[id]+=F(tr-tl+1);
}

inline ll query(int ql,int qr)
{
    ll res=0;int len=0,bl=bel[ql],br=bel[qr];
    if(bl==br)
    {
        for(re int i=ql;i<=qr;i++)
        {
            if(a[i]) len++;
            else res+=F(len),len=0;
        }
        return res+F(len);
    }
    for(re int i=ql;i<=R[bl];i++)
    {
        if(a[i]) len++;
        else res+=F(len),len=0;
    }
    for(re int i=bl+1;i<br;i++)
    {
        int Li=L[i],Ri=R[i];
        if(a[Li]&&r[Li]==Ri) len+=Ri-Li+1;
        else
        {
            int tl=r[Li]-L[i]+1,tr=Ri-l[Ri]+1;
            if(!a[Li]) tl=0;if(!a[Ri]) tr=0;
            res+=F(len+tl)-F(tl)-F(tr)+s[i];len=tr;
        }
    }
    for(re int i=L[br];i<=qr;i++)
    {
        if(a[i]) len++;
        else res+=F(len),len=0;
    }
    return res+F(len);
}

inline void sort_b()
{
    for(re int i=1;i<=n;i++) c[i]=0;
    for(re int i=1;i<=n;i++) c[b[i]]++;
    for(re int i=1;i<=n;i++) c[i]+=c[i-1];
    for(re int i=n;i>=1;i--) p[c[b[i]]--]=i;
}

inline void solve(int L,int R)
{
    for(re int i=1;i<=n;i++)
    {
        a[i]=l[i]=r[i]=s[i]=0;
        _b[i]=_l[i]=_r[i]=_s[i]=-1;
    }
    sort_b();k1=k2=0;
    for(re int i=L;i<=R;i++)
    {
        if(op[i]==1) q1[++k1]={x[i],y[i],i},_b[x[i]]=b[x[i]];
        else q2[++k2]={x[i],y[i],v[i],i};
    }
    sort(q1+1,q1+1+k1);
    sort(q2+1,q2+1+k2);
    for(re int i=1,j=1;i<=k2;i++)
    {
        int ql=q2[i].l,qr=q2[i].r,x=q2[i].x,id=q2[i].id;
        while(j<=n&&b[p[j]]<=x) {if(_b[p[j]]==-1) upd(p[j],0);j++;}
        for(re int j=1;j<=k1&&q1[j].id<id;j++) _b[q1[j].x]=q1[j].v;
        for(re int j=1;j<=k1;j++) if(_b[q1[j].x]<=x) upd(q1[j].x,1);
        ans[id]=query(ql,qr);
        for(int j=1;j<=k1;j++) a[q1[j].x]=0,_b[q1[j].x]=b[q1[j].x];
        for(int i=0;i<pl.size();i++) {int x=pl[i];if(~_l[x]) l[x]=_l[x],_l[x]=-1;}
        for(int i=0;i<pr.size();i++) {int x=pr[i];if(~_r[x]) r[x]=_r[x],_r[x]=-1;}
        for(int i=0;i<ps.size();i++) {int x=ps[i];if(~_s[x]) s[x]=_s[x],_s[x]=-1;}
        pl.clear(),pr.clear(),ps.clear();
    }
    for(int j=1;j<=k1;j++) b[q1[j].x]=q1[j].v;
}

int main()
{
    n=rd(),m=rd();
    for(re int i=1;i<=n;i++) b[i]=rd();
    for(re int i=1;i<=m;i++) {op[i]=rd(),x[i]=rd(),y[i]=rd();if(op[i]==2) v[i]=rd();}
    for(re int i=0;i<=n/B;i++)
    {
        L[i]=max(i*B,1),R[i]=min(i*B+B-1,n);
        for(re int j=L[i];j<=R[i];j++) bel[j]=i;
    }
    for(re int i=0;i<=m/B2;i++) solve(max(i*B2,1),min(i*B2+B2-1,m));
    for(re int i=1;i<=m;i++) if(op[i]==2) cout<<ans[i]<<'\n';
}

这下不得不觉得操作分块做法是狗屎了。

事实上,对于一个块,最多有 \(B\) 个版本。

设原序列为 \(b\)

即对于 \(\forall i \in [L_B,R_B]\),产生一个块内让所有 \(\leq b_i\) 的位置为 \(1\) 的版本。

块内排序,依次加入即可,维护方式同上,我们每个版本只需要记录:左端点极长段,右端点极长段,块内答案。

那么可以在 \(O(\sqrt n)\) 的时间内处理出所有版本。

对于一次询问 \(x\),找到块内 \(\leq x\) 的数的个数 \(k\),用版本 \(k\) 的信息即可。

注意到可能有 \(O(n\sqrt n)\) 次查询,\(O(n)\) 次插入,故用值域分块维护。

对于单点修改,暴力重构当前块内的所有版本。

但是有一个排序操作。事实上,我们只会改变一个位置,所以暴力往前 swap / 往后 swap 即可,复杂度 \(O(\sqrt n)\)

对于空间限制,我们离线下来逐块处理即可。

这个做法一点也不卡长,相对更好些(1.5 h),完爆操作分块。

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

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;
}

const int N=3e5+5,B=550,S=555;
ll s[S],ans[N];
int a[N],l[S],r[S],Lr[S],Rl[S];
int op[N],x[N],y[N],v[N],len[N];
int n,m,k,id,lenb,b[N],L[N],R[N],bel[N];
struct node{int v,i;} q[N];
struct block{
    int c1[N],c2[S];
    void clear() {memset(c1,0,sizeof(c1));memset(c2,0,sizeof(c2));}
    void upd(int x,int v)
    {
        int id=bel[x];
        for(int i=x;i<=R[id];i++) c1[i]+=v;
        for(int i=id+1;i<=n/B;i++) c2[i]+=v;
    }
    int ask(int x) {return c1[x]+c2[bel[x]];}
}vb;
inline ll F(int x) {return 1ll*x*(x+1)>>1;}

void upd(int x,int i)
{
    if(a[x]) {Lr[i]=Lr[i-1],Rl[i]=Rl[i-1],s[i]=s[i-1];return;}
    a[x]=1;
    int tl=x,tr=x;s[i]=s[i-1];
    if(a[x-1]) tl=l[x-1],s[i]-=F(x-l[x-1]);
    if(a[x+1]) tr=r[x+1],s[i]-=F(r[x+1]-x);
    l[x]=tl,r[x]=tr,r[tl]=tr,l[tr]=tl,s[i]+=F(tr-tl+1);
    Lr[i]=r[1],Rl[i]=l[lenb];
}

void build()
{
    for(int i=1;i<=S;i++) a[i]=l[i]=r[i]=Lr[i]=Rl[i]=0;
    for(int i=1;i<=k;i++) upd(q[i].i,i);
}

int main()
{
    n=rd(),m=rd();
    for(int i=1;i<=n;i++) b[i]=rd();
    for(int i=1;i<=m;i++) {op[i]=rd(),x[i]=rd(),y[i]=rd();if(op[i]==2) v[i]=rd();}
    for(int i=0;i<=n/B;i++)
    {
        L[i]=max(i*B,1),R[i]=min(i*B+B-1,n);
        for(int j=L[i];j<=R[i];j++) bel[j]=i;
    }
    for(id=0;id<=n/B;id++)
    {
        k=0,lenb=R[id]-L[id]+1;vb.clear();
        for(int i=L[id];i<=R[id];i++) q[++k]={b[i],i-L[id]+1},vb.upd(b[i],1);
        sort(q+1,q+1+k,[&](node a,node b){return a.v<b.v;});build();
        for(int j=1;j<=m;j++)
        {
            if(op[j]==1)
            {
                if(x[j]<L[id]||x[j]>R[id]) continue;
                vb.upd(b[x[j]],-1),vb.upd(b[x[j]]=y[j],1);
                for(int i=1;i<=k;i++)
                    if(q[i].i==x[j]-L[id]+1)
                    {
                        q[i].v=y[j];
                        while(i+1<=k&&q[i+1].v<q[i].v) swap(q[i],q[i+1]),i++;
                        while(i-1>=1&&q[i-1].v>q[i].v) swap(q[i],q[i-1]),i--;
                        break;
                    }
                build();
            }
            else
            {
                int l=x[j],r=y[j],k=vb.ask(v[j]);
                if(l<=L[id]&&R[id]<=r)
                {
                    if(Lr[k]==lenb) len[j]+=lenb;
                    else
                    {
                        int tl=Lr[k],tr=lenb-Rl[k]+1;
                        if(!Lr[k]) tl=0;if(!Rl[k]) tr=0;
                        ans[j]+=F(len[j]+tl)-F(tl)-F(tr)+s[k],len[j]=tr;
                    }
                }
                else
                {
                    l=max(l,L[id]),r=min(r,R[id]);
                    for(int i=l;i<=r;i++)
                    {
                        if(b[i]<=v[j]) len[j]++;
                        else ans[j]+=F(len[j]),len[j]=0;
                    }
                }
            }
        }
    }
    for(int i=1;i<=m;i++) if(op[i]==2) cout<<ans[i]+F(len[i])<<'\n';
}
posted @ 2023-10-10 20:40  spider_oyster  阅读(16)  评论(0编辑  收藏  举报