分块。我们先来考虑修改对整块的影响。记值域为 \(V=10^5\)。
考虑对每一块维护 \(V\) 个集合 \(S_1,S_2,\cdots,S_V\),第 \(i\) 个集合 \(S_i\) 维护了区间中所有 \(=i\) 的元素的一些信息,并维护区间的最大值 \(m\),对于一次操作 \(x\):
- 若 \(m\le 2x\),我们暴力对每个 \(i\in [x+1,m]\),将 \(S_i\) 合并进集合 \(S_{i-x}\)。
- 若 \(m>2x\),那么操作相当于先把 \(\le x\) 的数 \(+x\),再做全局 \(-x\)。我们对每个 \(i\in[1,x]\),把 \(S_i\) 合并进 \(S_{i+x}\),然后再给这个块打上标记 \(x\),表示这个块实际上的 \(=i\) 的元素构成的集合应该是 \(S_{i+x}\)。
我们设一次合并的复杂度为 \(O(f(n))\),那么对于第一种情况,我们用 \(m-x\) 次操作使 \(m\) 减小了 \(m-x\);对于第二种情况,我们用 \(x\) 次操作使 \(m\) 减小了 \(x\)。由于 \(m\le V\) 且 \(m\) 单调递减,因此一块的复杂度不会超过 \(O(V\times f(n))\),总的复杂度就不超过 \(O(V\sqrt{n}\times f(n))\)。
考虑应当怎样维护这样的集合的合并。注意我们需要在散块处进行重构,还需要应对查询,因此我们的 \(S_i\) 应当满足:
- 由 \(S_1,\cdots S_V\) 足以推出整个块的完整信息。
- 可以查询某个集合的大小 \(|S_i|\)。
那么只需要维护 \(S_i=\{j|a_j=i\}\),即所有 \(=i\) 的元素位置即可。具体实现的时候,我们可以一开始把所有相同的数全都连到最先出现的位置上,并记录每种元素第一次出现的位置;同时我们维护每种元素第一次出现时的值。也就是说,我们维护的块中,(实际的序列中)每种元素第一次出现的位置(在我们维护的序列中)的值应当是正确的。
那么一次操作假设要把 \(x\) 合并到 \(y\) 上面,我们就找到 \(r_x,r_y\),讨论一下:
- 如果 \(r_x\) 不存在那么什么都不用干。
- 如果 \(r_y\) 不存在,那么我们令 \(r_y\leftarrow r_x\),然后令 \(\text{val}(r_x)\leftarrow y\),最后把 \(r_x\) 置为 \(0\)。
- 否则我们找到 \(r_x,r_y\) 中较小的那一个 \(p\),令 \(\text{val}(p)\leftarrow y\),把 \(r_y\) 的父亲置为 \(p\),并令 \(r_x\leftarrow 0\)。
实际上这里维护的或许不能说是并查集,这更像一个链表,只不过是树形结构的。
如果要重构一块,由于我们保证了 \(i\) 的父亲在 \(i\) 前面,从前往后扫就行了。
直接做或许需要记录每一块的 \(r\) 序列导致空间复杂度达到 \(O(n\sqrt{n})\),考虑把询问全部离线,对每一块分别考虑他对所有修改与询问的贡献即可。这样空间复杂度就是线性,可以通过 P4117。
下面是 CF896E 的代码:
//-DYUNQIAN -std=c++14 -O2 -Wall
#include<bits/stdc++.h>
#define ll long long
using namespace std;
inline int read(){
int x=0,f=1;char c=getchar();
for(;(c<'0'||c>'9');c=getchar()){if(c=='-')f=-1;}
for(;(c>='0'&&c<='9');c=getchar())x=x*10+(c&15);
return x*f;
}
const int mod=1e9+7;
int ksm(int x,int y,int p=mod){
int ans=1;
for(int i=y;i;i>>=1,x=1ll*x*x%p)if(i&1)ans=1ll*ans*x%p;
return ans%p;
}
int inv(int x,int p=mod){return ksm(x,p-2,p)%p;}
mt19937 rnd(time(0));
int randint(int l,int r){return rnd()%(r-l+1)+l;}
void add(int &x,int v){x+=v;if(x>=mod)x-=mod;}
void Mod(int &x){if(x>=mod)x-=mod;}
void Assert(bool c,int L=0){if(!c){cout<<"Assertion Failed at "<<L<<endl;exit(0);}}
const int N=2e5+5;
const int MX=1e5;
int rt[N],fa[N],sz[N],val[N];
void merge(int x,int y){
if((!rt[x])||x>MX)return ;
if(rt[y]){
int p=min(rt[x],rt[y]);
fa[rt[y]]=fa[rt[x]]=p,sz[p]=sz[rt[x]]+sz[rt[y]];
if(rt[x]!=p)sz[rt[x]]=0;
if(rt[y]!=p)sz[rt[y]]=0;
val[p]=y,rt[y]=p,rt[x]=0;
}
else rt[y]=rt[x],val[rt[x]]=y,rt[x]=0;
}
int V,tag;
void build(int l,int r){
V=0;
for(int i=l;i<=r;i++){
V=max(V,val[i]);
if(rt[val[i]])fa[i]=rt[val[i]],sz[rt[val[i]]]++;
else rt[val[i]]=i,sz[i]=1,fa[i]=i;
}
}
void clear(int l,int r){
for(int i=l;i<=r;i++)sz[rt[val[i]]]=0,rt[val[i]]=0,val[i]=val[fa[i]];
for(int i=l;i<=r;i++)val[i]-=tag;
tag=0;
}
void sub(int x){
if(V-tag<=x)return ;
else if(V-tag<=x+x){for(int i=x+1;i<=V-tag;i++)merge(i+tag,i+tag-x);V=min(V,x+tag);}
else{for(int i=1;i<=x;i++)merge(i+tag,i+tag+x);tag+=x;}
}
int op[N],st[N],ed[N],qv[N],n,m,a[N],ans[N];
int W=0;
void solve(int l,int r){
for(int i=l;i<=r;i++)val[i]=a[i];tag=0;
for(int i=1;i<=W;i++)rt[i]=sz[i]=0;
build(l,r);
for(int t=1;t<=m;t++){
int ql=max(l,st[t]),qr=min(r,ed[t]),x=qv[t];
if(ql>qr)continue;
if(op[t]==1){
if(ql==l&&qr==r)sub(x);
else{
clear(l,r);
for(int i=ql;i<=qr;i++)if(val[i]>x)val[i]-=x;
build(l,r);
}
}
else{
if(ql==l&&qr==r){ans[t]+=sz[rt[x+tag]];continue;}
clear(l,r);
for(int i=ql;i<=qr;i++)if(val[i]==x)ans[t]++;
build(l,r);
}
}
}
signed main(void){
#ifdef YUNQIAN
freopen("896E.in","r",stdin);
#endif
n=read(),m=read();
for(int i=1;i<=n;i++)a[i]=read(),W=max(W,a[i]);
for(int i=1;i<=m;i++)op[i]=read(),st[i]=read(),ed[i]=read(),qv[i]=read();
int B=(int)(sqrt(n));
for(int i=1;i<=n;i+=B)solve(i,min(i+B-1,n));
for(int i=1;i<=m;i++)if(op[i]==2)cout<<ans[i]<<'\n';
return 0;
}
标签:P4117,Ynoi2018,洛谷,val,sz,int,复杂度,rt,mod
From: https://www.cnblogs.com/YunQianQwQ/p/17494613.html