【洛谷4119】[Ynoi2018] 未来日记(分块+并查集)

点此看题面

  • 给定一个长度为(n)的序列,要求支持两种操作:把一个区间中所有(x)修改为(y),或询问区间第(k)小值。
  • (n,qle10^5)

奋战(4h)的成果。。。人生第二次不看题解切黑色Ynoi。(sto yzxoi orz)

对于序列的分块

首先我们需要对于原序列分块。

对于整块,考虑把一种数字全部变成另一种数字,当然可以搞一些数据结构合并,但最好也最快的肯定还是并查集

而对于散块,我们只要暴力枚举修改,然后重构这个序列的并查集即可。(注意,我们不需要重构其他颜色的并查集,只需把被修改的数字(x)合并到修改成的数字(y)的并查集中,并对剩余的(x)重构并查集,可以减小常数,毕竟这题极度卡常)

然后发现这样一来我们就可以处理好修改,并维护出每个块中每种颜色的个数,但依旧无法高效处理询问。

对于值域的分块

为了高效处理询问,我们还需要搞一个值域分块。

又因为是区间查询,还要在这个值域分块里面套上一个序列分块。

具体地,我们开两个前缀和数组(p_{i,v})(q_{i,s}),分别记录前(i)个块中,值为(v)的数的个数和值在第(s)个值域块的数的个数。

在修改的时候,由于实际上只会影响到两种值的个数,我们枚举每个块的时候只需先单点修改,而在一次修改完全结束之后再对这两个值重新做一遍前缀和。

实际询问中,对于散块直接枚举所有元素,用临时数组存一下每种值的个数和每个值域块中数的个数,和维护好的中间整块中的信息放在一起,先枚举第(k)大在哪个值域块中,再在这个块中枚举具体值即可。

口胡起来很简单,具体操作起来就细节巨多,心态简直爆炸。(其实我是因为一个基本的语法问题挂掉的,一开始改掉了,结果后来复制了一遍之前的代码忘记再改回去,(WA)了半天)

代码:(O(nsqrt n))

#include<bits/stdc++.h>
#define Tp template<typename Ty>
#define Ts template<typename Ty,typename... Ar>
#define Reg register
#define RI Reg int
#define Con const
#define CI Con int&
#define I inline
#define W while
#define N 100000
#define BS 640
#define BT 160
#define VT 320
using namespace std;
int n,a[N+5],sz,bl[N+5];
namespace FastIO
{
	#define FS 100000
	#define tc() (FA==FB&&(FB=(FA=FI)+fread(FI,1,FS,stdin),FA==FB)?EOF:*FA++)
	#define pc(c) (FC==FE&&(clear(),0),*FC++=c)
	int OT;char oc,FI[FS],FO[FS],OS[FS],*FA=FI,*FB=FI,*FC=FO,*FE=FO+FS;
	I void clear() {fwrite(FO,1,FC-FO,stdout),FC=FO;}
	Tp I void read(Ty& x) {x=0;W(!isdigit(oc=tc()));W(x=(x<<3)+(x<<1)+(oc&15),isdigit(oc=tc()));}
	Ts I void read(Ty& x,Ar&... y) {read(x),read(y...);}
	Tp I void writeln(Ty x) {W(OS[++OT]=x%10+48,x/=10);W(OT) pc(OS[OT--]);pc('
');}
}using namespace FastIO;
int Mn;namespace V//值域分块
{
	int sz,bl[N+5];I void Build() {sz=sqrt(N);for(RI i=1;i<=N;++i) bl[i]=(i-1)/sz+1;}//初始化每个数在哪个块中
	int p[BT+5][N+5],q[BT+5][VT+5],sp[BT+5][N+5],sq[BT+5][VT+5];
	I void U(RI i,CI x,CI v) {v&&(Mn=min(Mn,i)),p[i][x]+=v,q[i][bl[x]]+=v;}//先单点修改
	I void F5(CI x) {for(RI i=Mn;i<=::bl[n];++i) sp[i][x]=sp[i-1][x]+p[i][x],sq[i][bl[x]]=sq[i-1][bl[x]]+q[i][bl[x]];}//做前缀和
	I int Q(CI l,CI r,CI v) {return sp[r][v]-sp[l-1][v];}I int QQ(CI l,CI r,CI s) {return sq[r][s]-sq[l-1][s];}//区间询问,差分
}
int T,S[BS+5];struct Block
{
	int id,n,a[BS+5],lnk[N+5];I int& operator [] (CI x) {return a[fa(x)];}
	int f[BS+5];I int fa(CI x) {return f[x]?f[x]=fa(f[x]):x;}//并查集
	I void Build() {for(RI i=1;i<=n;++i) lnk[a[i]]?f[i]=lnk[a[i]]:lnk[a[i]]=i;}//初始化,把相同值合并
	I void BF(CI l,CI r,CI x,CI y)//散块暴力修改
	{
		RI i,t=0;for(i=1;i<=n;++i) (a[i]=a[fa(i)])==x&&(S[++T]=i);//存下所有x
		for(i=1;i<=T;++i) f[S[i]]=0,l<=S[i]&&S[i]<=r&&(a[S[i]]=y,++t);V::U(id,x,-t),V::U(id,y,t);//修改区间中的x,在值域分块中更新
		lnk[x]=0;W(T) lnk[a[S[T]]]?f[S[T]]=lnk[a[S[T]]]:lnk[a[S[T]]]=S[T],--T;//重构x的并查集,被修改的加入y
	}
	I void U(CI x,CI y)//整块修改
	{
		if(!lnk[x]) return;RI t=V::p[id][x];V::U(id,x,-t),V::U(id,y,t);//在值域分块中更新
		lnk[y]?f[lnk[x]]=lnk[y]:(a[lnk[x]]=y,lnk[y]=lnk[x],0),lnk[x]=0;//并查集合并
	}
}B[BT+5];
int bg[N+5];I void Build()//初始化
{
	RI i;for(sz=2*sqrt(n),i=1;i<=n;++i) !B[bl[i]=(i-1)/sz+1].n&&(bg[bl[i]]=i-1),B[bl[i]][++B[bl[i]].n]=a[i];//建出每个块
	for(i=1;i<=bl[n];++i) B[B[i].id=i].Build();for(i=1;i<=n;++i) V::U(bl[i],a[i],1);for(i=1;i<=N;++i) V::F5(i);//对每个块初始化,对每种值做前缀和
}
I void U(CI l,CI r,CI x,CI y)//修改
{
	RI L=bl[l],R=bl[r];if(x==y) return;if(L==R) return B[L].BF(l-bg[L],r-bg[L],x,y);//同块
	B[L].BF(l-bg[L],B[L].n,x,y),B[R].BF(1,r-bg[R],x,y);for(RI i=L+1;i<R;++i) B[i].U(x,y);//散块暴力修改,整块并查集合并
}
int c[N+5],d[VT+5];I int Q(CI l,CI r,CI k)//询问
{
	RI i,ans,t=0,L=bl[l],R=bl[r];if(L==R)//同块
	{
		for(i=l-bg[L];i<=r-bg[L];++i) ++c[B[L][i]],++d[V::bl[B[L][i]]];//全部加入临时数组
		for(i=1;t+d[i]<k;++i) t+=d[i];for(i=(i-1)*V::sz+1;t+c[i]<k;++i) t+=c[i];ans=i;//值域分块询问
		for(i=l-bg[L];i<=r-bg[L];++i) --c[B[L][i]],--d[V::bl[B[L][i]]];return ans;//从临时数组中删去
	}
	for(i=l-bg[L];i<=B[L].n;++i) ++c[B[L][i]],++d[V::bl[B[L][i]]];//左侧散块加入加入临时数组
	for(i=1;i<=r-bg[R];++i) ++c[B[R][i]],++d[V::bl[B[R][i]]];//右侧散块加入加入临时数组
	for(i=1;t+d[i]+V::QQ(L+1,R-1,i)<k;++i) t+=d[i]+V::QQ(L+1,R-1,i);//找到在哪个块
	for(i=(i-1)*V::sz+1;t+c[i]+V::Q(L+1,R-1,i)<k;++i) t+=c[i]+V::Q(L+1,R-1,i);ans=i;//找到具体值
	for(i=l-bg[L];i<=B[L].n;++i) --c[B[L][i]],--d[V::bl[B[L][i]]];//删去左侧散块
	for(i=1;i<=r-bg[R];++i) --c[B[R][i]],--d[V::bl[B[R][i]]];return ans;//删去右侧散块
}
int main()
{
	RI Qt,i;for(read(n,Qt),i=1;i<=n;++i) read(a[i]);V::Build(),Build();//初始化
	RI op,l,r,x,y;W(Qt--) read(op,l,r,x),op==1?
		(Mn=1e9,read(y),U(l,r,x,y),V::F5(x),V::F5(y)):writeln(Q(l,r,x));return clear(),0;
}
败得义无反顾,弱得一无是处
原文地址:https://www.cnblogs.com/chenxiaoran666/p/Luogu4119.html