P4117 [Ynoi2018] 五彩斑斓的世界

题目大意

详细题目传送门
一个长为 \(n\) 的序列 \(a\),有 \(m\) 次操作

  1. 把区间 \([l,r]\) 中大于 \(x\) 的数减去 \(x\)
  2. 查询区间 \([l,r]\)\(x\) 的出现次数。

\(1\le n\le 10^6\)\(1\le m\le 5\times 10^5\)\(0 \le a_i,x \le 10^5+1\)

注意时限 \(7.5\) 秒且内存限制 \(64MB\)

思路

一个 Ynoi 神秘套路第二分块(似乎是这个名字)。

发现 \(n\) 很大,如果按询问考虑的话每一次每个块不好处理。于是考虑一个 trick 逐快处理。即将每一个询问离线下来,对于每一个个块去遍历询问按这个询问这个块是整块还是散块查询。于是这个时间复杂度就是 \(O(m\sqrt{n})\) 的可以接受。

之后需要考虑均摊 \(O(1)\) 维护操作。观察到值域不大,这给了我们提示。发现每一次如果维护这个大于\(x\) 的操作就不好做,因为它在每个块内是需要查询不同位置的,如果需要维护则至少要一个 \(V\) 的时间,就无法接受了。

考虑转化。维护每个块的可能最大值 \(k_{\max}\),考虑两种情况:

  1. \(k_{\max}\leq2x\) 时,则这次询问之后将没有元素比 \(x\) 大,于是把所有比 \(x\) 的值全部减 \(x\),所以此时最大值最多为 \(x\)
  2. 否则我们把所有小于 \(x\) 的值加 \(x\),然后给这个块打一个 \(x\) 的懒标记,就相当于最大值减了 \(x\)

这样做的好处是什么呢。发现在这种情况下,最大值和最小值会逐渐接近,在最终重叠的情况下就不变了。这样就不用考虑具体的值,只在值域上维护信息。

具体怎么维护那个减加操作呢,我们需要一个操作是 \(O(1)\) 的东西,然后谁?能想到并查集。

我们开一个值域并查集,然后对于每一次操作,相当于将 \(v\) 指向 \(v\pm x\),然后 \(v\) 点消失。这样维护的带权并查集没有深度,只在根上进行,所以操作都是 \(O(1)\) 的。但是要注意具体实现时的循环顺序,否则会出现像类似于 01 背包那种多考虑的情况(这是后话)。

上文都是在考虑整块,所以查询就查询并查集维护的那个点即可。考虑如果做散块。

对于散块可以直接重构,因为一个询问最多在两个散块中存在,所以重构也是 \(O(n\sqrt{n})\) 的。具体重构就是我们在并查集合并时也维护一下每一个 \(a_i\) 的最终位置 \(a^{'}_i\)

这样我们就做到了线性空间和 \(O(n\sqrt{n})\) 的时间内完成了这道题目。

代码

#include<bits/stdc++.h>
using namespace std;
typedef int ll;
const ll MAXN=1e6+10;
const ll MAXM=5e5+10;
const ll MAXV=2e5+10;
ll n,m;
ll a[MAXN];
struct Query{
    ll op,l,r,x,ans;
}q[MAXM];
struct Kuai{
    ll l,r,mx,tag;
}k[1005];
ll f[MAXN],rt[MAXV],val[MAXN],cnt[MAXV];
inline ll find(ll u){
    return f[u]==u?u:f[u]=find(f[u]);
}
inline void merge(ll u,ll v){
    if(!rt[u]){
        return;
    }
    if(rt[v]){
        f[rt[u]]=rt[v];
    }else{
        val[rt[u]]=v;
        rt[v]=rt[u];
    }
    cnt[v]+=cnt[u];
    rt[u]=cnt[u]=0;
}
inline void init(ll T){
    ll l=k[T].l,r=k[T].r;
    for(register int i=l;i<=r;++i){
        cnt[a[i]]++;
        k[T].mx=max(k[T].mx,a[i]);
    }
    for(register int i=l;i<=r;++i){
        if(rt[a[i]]){
            f[i]=rt[a[i]];
        }else{
            rt[a[i]]=f[i]=i;
            val[i]=a[i];
        }
    }
}
inline void modify_z(ll T,ll x){
    ll mx=k[T].mx,tag=k[T].tag;
    if(2*x>=mx-tag){
        for(register int i=mx;i>x+tag;--i){
            merge(i,i-x);
        }
        if(mx-tag>x){
            mx=x+tag;
        }
    }else{
        while (!cnt[mx]) mx--;
        for(register int i=tag+1;i<=x+tag;++i){
            merge(i,i+x);
        }
        tag+=x;
    }
    k[T].mx=mx;
    k[T].tag=tag;
}
inline void modify_s(ll T,ll l,ll r,ll x){
    for(int i=k[T].l;i<=k[T].r;++i){
        a[i]=val[find(i)];
        cnt[a[i]]=rt[a[i]]=0;
        a[i]-=k[T].tag;
    }
    for(register int i=max(k[T].l,l);i<=min(k[T].r,r);++i){
        if(a[i]>x){
            a[i]-=x;
        }
    }
    k[T].tag=0;
    k[T].mx=0;
    init(T);
}
inline void query_s(ll T,ll id){
    for(register int i=max(k[T].l,q[id].l);i<=min(k[T].r,q[id].r);++i){
        if(val[find(i)]-k[T].tag==q[id].x){
            q[id].ans++;
        }
    }
}
int main(){
	ios::sync_with_stdio(false);
	cin.tie(0);cout.tie(0);
	cin>>n>>m;
	for(register int i=1;i<=n;++i){
	    cin>>a[i];
	}
	for(register int i=1;i<=m;++i){
	    cin>>q[i].op>>q[i].l>>q[i].r>>q[i].x;
	}
    ll kl=sqrt(n);
    for(register int i=1;i<=kl;++i){
        k[i].l=k[i-1].r+1;
        k[i].r=k[i].l+kl-1;
    }
    if(k[kl].r<n){
        kl++;
        k[kl].l=k[kl-1].r+1;
        k[kl].r=n;
    }
    for(register int T=1;T<=kl;++T){
        memset(rt,0,sizeof rt);
        memset(cnt,0,sizeof cnt);
        init(T);
        for(register int i=1;i<=m;++i){
            if(k[T].r<q[i].l||q[i].r<k[T].l||(q[i].op==1&&q[i].x==0)){
                continue;
            }
            if(q[i].x>k[T].mx){
                continue;
            }
            if(q[i].l<=k[T].l&&k[T].r<=q[i].r){
                if(q[i].op==1){
                    modify_z(T,q[i].x);
                }else{
                    if(q[i].x+k[T].tag>=MAXV){
                        continue;
                    }
                    q[i].ans+=cnt[q[i].x+k[T].tag];
                }
            }else{
                if(q[i].op==1){
                    modify_s(T,q[i].l,q[i].r,q[i].x);
                }else{
                    query_s(T,i);
                }
            }
        }
    }
    for(int i=1;i<=m;++i){
        if(q[i].op==2){
            cout<<q[i].ans<<endl;
        }
    }
	return 0;
}
posted @   tanghg  阅读(3)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 清华大学推出第四讲使用 DeepSeek + DeepResearch 让科研像聊天一样简单!
· 推荐几款开源且免费的 .NET MAUI 组件库
· 实操Deepseek接入个人知识库
· 易语言 —— 开山篇
· Trae初体验
点击右上角即可分享
微信分享提示