【洛谷4117】[Ynoi2018] 五彩斑斓的世界(第二分块)
- 给定一个长度为\(n\)的序列,要求支持两种操作:将区间内大于\(x\)的数减去\(x\);询问区间中\(x\)的出现次数。
- \(n\le10^6,q\le5\times10^5,V\le10^5\)
第二分块
突刺贯穿的第二分块
首先观测这道题极度卡内存,肯定无法对于每个块分别开一个\(O(V)\)的数组。
但是,这道题中不同的位置之间实际上并没有任何影响,因此我们有一个奇妙的想法,就是离线,枚举每个块去处理每个操作。
考虑记录每个块的最大值\(Mx\),对于当前的修改值\(x\),分两类讨论:
- 如果\(x>\frac{Mx}2\):直接把所有大于\(x\)的数减去\(x\)。
- 如果\(x\le\frac{Mx}2\):把所有小于等于\(x\)的数加上\(x\),然后打上一个全局减\(x\)的标记。
由于每次这样操作之后最大值只会越来越小,一个块的总复杂度应该是\(O(V)\)的。
至于如何有效实现这里的加减法,显然不可能枚举每个数,而是应该枚举每种数(如果一种很大的数有许多个,显然复杂度还是会挂)。
如果当前块是散块,直接暴力重构并查集。
如果当前块是整块,就枚举需要修改的那些数在并查集上合并。为此需要记录每种颜色的根节点。
询问时散块直接暴力,整块直接询问连通块大小即可。
于是就结束了。
代码:\(O(q\sqrt n)\)
#include<bits/stdc++.h>
#define Tp template<typename Ty>
#define Ts template<typename Ty,typename... Ar>
#define Reg register
#define RI Reg int
#define Con const
#define CI Con int&
#define I inline
#define W while
#define N 1000000
#define M 500000
#define V 100001
#define BT 1000
#define BS 1000
using namespace std;
int n,sz,a[N+5],ans[M+5];struct Q {int op,l,r,v;}q[M+5];
namespace FastIO
{
#define FS 100000
#define tc() (FA==FB&&(FB=(FA=FI)+fread(FI,1,FS,stdin),FA==FB)?EOF:*FA++)
#define pc(c) (FC==FE&&(clear(),0),*FC++=c)
int OT;char oc,FI[FS],FO[FS],OS[FS],*FA=FI,*FB=FI,*FC=FO,*FE=FO+FS;
I void clear() {fwrite(FO,1,FC-FO,stdout),FC=FO;}
Tp I void read(Ty& x) {x=0;W(!isdigit(oc=tc()));W(x=(x<<3)+(x<<1)+(oc&15),isdigit(oc=tc()));}
Ts I void read(Ty& x,Ar&... y) {read(x),read(y...);}
Tp I void writeln(Ty x) {W(OS[++OT]=x%10+48,x/=10);W(OT) pc(OS[OT--]);pc('\n');}
}using namespace FastIO;
struct Block
{
int n,fg,Mx,a[BS+5],lnk[V+5];I int& operator [] (CI x) {return a[x];}
int f[BS+5],g[BS+5];I int fa(CI x) {return f[x]?f[x]=fa(f[x]):x;}
I void Build() {Mx=0;for(RI i=1;i<=n;++i) lnk[a[i]]?++g[f[i]=lnk[a[i]]]:g[lnk[a[i]]=i]=1,Mx=max(Mx,a[i]);}//初始化,建并查集
I void Cl() {for(RI i=1;i<=n;++i) lnk[a[i]=a[fa(i)]]=0,f[i]=0;for(RI i=1;i<=n;++i) a[i]-=fg;fg=0;}//清空一切,清除标记
I void Union(CI x,CI y) {lnk[y]&&(lnk[x]?g[f[lnk[y]]=lnk[x]]+=g[lnk[y]]:a[lnk[x]=lnk[y]]=x,lnk[y]=0);}//并查集上合并
I int Q(CI v) {return fg+v<=V&&lnk[fg+v]?g[lnk[fg+v]]:0;}//询问连通块大小
I int G(CI l,CI r,CI v) {RI t=0;for(RI i=l;i<=r;++i) a[fa(i)]==fg+v&&++t;return t;}//暴力询问
I void U(CI v)//整块修改
{
if(2*v>Mx-fg) {for(RI i=fg+v+1;i<=Mx;++i) Union(i-v,i);Mx=min(Mx,fg+v);}//如果v>Mx/2
else {for(RI i=fg+v;i>=fg+1;--i) Union(i+v,i);fg+=v;}//如果v≤Mx/2
}
I void BF(CI l,CI r,CI v)//暴力修改
{
RI i;for(Cl(),i=l;i<=r;++i) a[i]>v&&(a[i]-=v);for(fg=Mx=0,i=1;i<=n;++i) Mx=max(Mx,a[i]);Build();//修改前清空标记,修改后重构
}
}B;
int main()
{
RI Qt,i,j;for(read(n,Qt),i=1;i<=n;++i) read(a[i]);for(i=1;i<=Qt;++i) read(q[i].op,q[i].l,q[i].r,q[i].v);
RI L,R,op,l,r,v;for(sz=sqrt(n),i=1;i<=(n-1)/sz+1;++i)
{
for(L=(i-1)*sz+1,R=min(i*sz,n),B.Cl(),B.n=0,j=L;j<=R;++j) B[++B.n]=a[j];//把元素加到块中
for(B.Build(),j=1;j<=Qt;++j) L<=q[j].r&&R>=q[j].l&&
(
op=q[j].op,l=max(q[j].l-L+1,1),r=min(q[j].r-L+1,B.n),v=q[j].v,
l==1&&r==B.n?(op==1?(B.U(v),0):ans[j]+=B.Q(v)):(op==1?(B.BF(l,r,v),0):ans[j]+=B.G(l,r,v))//整块/散块
);
}
for(i=1;i<=Qt;++i) q[i].op==2&&(writeln(ans[i]),0);return clear(),0;
}
待到再迷茫时回头望,所有脚印会发出光芒