[Ynoi2018] いろとりどりのセカイ

「突刺贯穿的第二分块」

Luogu P4117

CF896E

Solution (OldDriverTree)

Prob

二阶堂真红给了你一个长为 \(n\) 的序列 \(a\),有 \(m\) 次操作

  1. 把区间 \([l,r]\) 中大于 \(x\) 的数减去 \(x\)
  2. 查询区间 \([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)

  • 过度依赖题解而没有自己的理解。

  • 常数太大。

posted @ 2025-03-11 20:55  JoeyJiang  阅读(12)  评论(0)    收藏  举报