[Ynoi2018] いろとりどりのセカイ
「突刺贯穿的第二分块」
Link
Prob
二阶堂真红给了你一个长为 \(n\) 的序列 \(a\),有 \(m\) 次操作
- 把区间 \([l,r]\) 中大于 \(x\) 的数减去 \(x\)。
- 查询区间 \([l,r]\) 中 \(x\) 的出现次数。
1 l r x:把区间 \([l,r]\) 所有大于 \(x\) 的数减去 \(x\)。2 l r x:查询区间 \([l,r]\) 内的 \(x\) 的出现次数。
对于 \(100\%\) 的数据,\(1\le n\le 10^6\),\(1\le m\le 5\times 10^5\),\(1\le l\le r \le n\),\(0 \le a_i,x \le 10^5+1\)。
Time : 7.5s
Memory : 64 MiB
LXL Diff : 6
CF Diff : *3100
Solution
考虑分块。
考虑这个块内部的最大值,不妨将其设置为 \(mx\),并设置一个区间减的 tag(因为我们只会将数值减小而不会增大)。
-
如果 \(mx\leq 2x\) ,则我们直接把所有大于 \(x\) 的数减去 \(x\)。
-
如果 \(mx\geq 2x\) ,则我们直接把所有小于 \(x\) 的数加上 \(x\) 并打上区间减的标记。
然后最大值随之更新即可。
由于最大值与 \(n\) 同阶,因此块内总复杂度均摊 \(O(n)\)。
我们希望对每个不同的值的修改能做到 \(O(1)\)。
考虑使用并查集把相同的值并起来。那么修改的时候,只需要把修改前值对应的并查集的根,连到修改后的值的并查集的根上即可。同时我们需要记录每个数的出现次数,修改的时候直接加过去就好了。
-
整块 modify:同上,见
inline void modify(int x)。 -
散块 modify:重构,见
inline void modify(int b,int l,int r,int x)。 -
整块 query:直接由并查集查询,即
siz[qv+tag]。 -
散块 query:枚举统计,见
inline int query(int l,int r,int x)。
总时间复杂度 \(O(n\sqrt n\alpha(n))\),空间复杂度 \(O(n\sqrt n)\)。
发现块与块间是独立的,所以分开处理。
空间复杂度优化至 \(O(n)\)。
Code
Max Mem : 44.11 MiB
Average Time : 5.15 s
Length : 3.08 KiB
#include<bits/stdc++.h>
using namespace std;
const int N=1e6+9;
const int sN=1e3+9;
#define endl '\n'
#define rint register int
inline int read(){
register int x=0;
register bool f=0;
register char c=getchar();
while(c<'0'||c>'9'){
if(c=='-') f=1;
c=getchar();
}
while(c>='0'&&c<='9'){
x=(x<<3)+(x<<1)+c-48;
c=getchar();
}
return f?-x:x;
}
char cr[200];int tt;
inline void print(register int x,register char k='\n') {
if(!x) putchar('0');
if(x < 0) putchar('-'),x=-x;
while(x) cr[++tt]=x%10+'0',x/=10;
while(tt) putchar(cr[tt--]);
putchar(k);
}
int a[N];
int op[N],l[N],r[N],k[N],ans[N];
int L[N],R[N],blk[N];
int tag=0,mx=0;
int fa[N],ra[N],val[N],siz[N];
inline void build(int b){
tag=0,mx=0;
memset(ra,0,sizeof(ra));
memset(siz,0,sizeof(siz));
for(rint i=L[b];i<=R[b];i++){
mx=max(mx,a[i]);
if(!ra[a[i]]){
ra[a[i]]=i;
fa[i]=i;
val[i]=a[i];
}else fa[i]=ra[a[i]];
siz[a[i]]++;
}
}
inline int find(int x){
if(fa[x]==x) return x;
else return fa[x]=find(fa[x]);
}
inline void merge(int x,int y){
if(ra[x]) fa[ra[y]]=ra[x];
else ra[x]=ra[y],val[ra[x]]=x;
siz[x]+=siz[y];
siz[y]=ra[y]=0;
}
inline void modify(int x){
if((x<<1)+tag>mx){
for(rint i=x+tag+1;i<=mx;++i) if(ra[i]) merge(i-x,i);
if(tag+x<mx) mx=x+tag;
}else{
for(rint i=x+tag;i>=tag;--i) if(ra[i]) merge(i+x,i);
tag+=x;
}
}
inline void modify(int b,int l,int r,int x){
for(rint i=L[b];i<=R[b];i++){
a[i]=val[find(i)];
ra[a[i]]=siz[a[i]]=0;
a[i]-=tag;
}
for(rint i=L[b];i<=R[b];i++) val[i]=0;
for(rint i=l;i<=r;i++) if(a[i]>x) a[i]-=x;
tag=0,mx=0;
for(rint i=L[b];i<=R[b];i++){
mx=max(mx,a[i]);
if(!ra[a[i]]){
ra[a[i]]=i;
fa[i]=i;
val[i]=a[i];
}else fa[i]=ra[a[i]];
siz[a[i]]++;
}
}
inline int query(int l,int r,int x){
if(x+tag>5e5) return 0;
int cnt=0;
for(rint i=l;i<=r;i++) if(val[find(i)]==x+tag) cnt++;
return cnt;
}
int main(){
int n=read(),m=read();
for(rint i=1;i<=n;i++) a[i]=read();
for(rint i=1;i<=m;i++) op[i]=read(),l[i]=read(),r[i]=read(),k[i]=read();
int B=sqrt(n);
for(rint i=1;i<=n;i++) blk[i]=(i-1)/B+1;
for(rint i=1;i<=n;i++){
if(!L[blk[i]]) L[blk[i]]=i;
R[blk[i]]=i;
}
for(rint i=1;i<=blk[n];i++){
build(i);
for(rint j=1;j<=m;j++){
int ql=max(l[j],L[i]),qr=min(r[j],R[i]),qv=k[j];
if(ql>qr) continue ;
if(op[j]==1){
if(ql==L[i]&&qr==R[i]) modify(qv);
else modify(i,ql,qr,qv);
}else{
if(ql==L[i]&&qr==R[i]) ans[j]+=siz[qv+tag];
else ans[j]+=query(ql,qr,qv);
}
}
}
for(rint i=1;i<=m;i++) if(op[i]==2) print(ans[i]);
return 0;
}
Tricks
-
块与块分开处理。
-
正难则反。
-
并查集维护值域。
Mistakes
-
fa[x]=find(fa[x]) -> fa[x]=find(x) -
for(rint i=x+tag;i>=tag;--i) -> for(rint i=mx+tag;i>=tag;--i) -
过度依赖题解而没有自己的理解。
-
常数太大。

浙公网安备 33010602011771号