zoukankan      html  css  js  c++  java
  • 【洛谷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;
    }
    
    败得义无反顾,弱得一无是处
  • 相关阅读:
    剑指 Offer——13. 调整数组顺序使奇数位于偶数前面
    剑指 Offer——3. 从尾到头打印链表
    剑指 Offer——2. 替换空格
    剑指 Offer——1. 二维数组中的查找
    LeetCode 905. Sort Array By Parity 按奇偶校验排列数组
    LeetCode 448. Find All Numbers Disappeared in an Array找到所有数组中消失的元素
    SSH 代码笔记
    anaconda3安装caffe
    opencv多版本安装
    人脸文章与数据库
  • 原文地址:https://www.cnblogs.com/chenxiaoran666/p/Luogu4119.html
Copyright © 2011-2022 走看看