分块进阶
广告:分块入门
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';
}