CF896E/洛谷 P4117 [Ynoi2018]五彩斑斓的世界/Welcome home, Chtholly

发布时间 2023-06-20 20:26:23作者: 云浅知处

分块。我们先来考虑修改对整块的影响。记值域为 \(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;
}