它来了!
分析一下第一个操作,不是写过嘛,并查集
分析一下第二个操作,二分套二分答案
拿下了这题
仔细分析,貌似时间复杂度是错的
我们考虑块套块 时间复杂度
对1e5的值域进行分块
求k值我们可以先找是第几个块,再找到是块内第几个数
如何做?
散块
我们可以做一个桶来临时存数量
整块
我们可以做一个前缀和,O(1)询问范围内的数量与k的关系,小于k我们就累加,否则我们就找到了那个块,找到第几个数也是一样的原理
因此我们维护bc[i][j] i个块内 j个值域块内的数量,c[i][j] i个块内j个数值的数量,那么散块也需要临时维护t1[i]第i个值域块的数量,t2[i]第i个数值的数量,提前预处理
如果只有一个散块可以直接找nth_element
inline int kth(int l,int r,int k){int p=pos[l];int q=pos[r];ll res=0;ll sum=0;if(p==q){pushdown(p);for(int i=l;i<=r;i++){t2[i]=a[i];}nth_element(t2+l,t2+l+k-1,t2+r+1);res=t2[l+k-1];//0->kfor(int i=l;i<=r;i++){t2[i]=0;//清空}}else{//散块计数pushdown(p);for(int i=l;i<=R[p];i++){t1[PP[a[i]]]++;t2[a[i]]++;}pushdown(q);for(int i=L[q];i<=r;i++){t1[PP[a[i]]]++;t2[a[i]]++;}//整块处理for(int i=1;i<=tt;i++){//枚举值域块if(sum+t1[i]+bc[q-1][i]-bc[p][i]>=k){//[p+1,q-1]for(int j=LL[i];j<=RR[i];j++){//枚举块内值域if(sum+t2[j]+c[q-1][j]-c[p][j]>=k){//找到位置//查询结束前清空for(int o=l;o<=R[p];o++){//[l,R[p]]t1[PP[a[o]]]--;t2[a[o]]--;}for(int o=L[q];o<=r;o++){//[R[q],r]t1[PP[a[o]]]--;t2[a[o]]--;}//返回答案return j;//kth}else{sum+=(t2[j]+c[q-1][j]-c[p][j]);}}}else{sum+=(t1[i]+bc[q-1][i]-bc[p][i]);//累加}}}return res;
}
结合操作一
我们维护val[i][j] 记录在第i块内的第j个位置的值,index[i][j]记录在第i个块内的a[j]在什么位置
loc[i]记录index[i][a[i]]
预处理
inline void build(int id){//建立关系int cur=0;//根标号for(int i=1;i<=blo;i++){//清空关系index[id][val[id][i]]=0;}for(int i=L[id];i<=R[id];i++){if(!index[id][a[i]]){cur++;index[id][a[i]]=cur;val[id][cur]=a[i];}loc[i]=index[id][a[i]];}
}
这样我们可以通过3个数组推出a[i]的真实值
inline void pushdown(int id){//还原数组for(int i=L[id];i<=R[id];i++){a[i]=val[id][loc[i]];//}
}
合并操作
inline void merge(int id,int x,int y){//合并index[id][y]=index[id][x];val[id][index[id][x]]=y;index[id][x]=0;
}
修改我们考虑差分,再单块处理后,前缀和,时间复杂度不变
散块
直接modify
整块
如果没有x,直接跳过
如果没有x,有y 转移贡献
如果有x,有y +合并
inline void modify(int l,int r,int x,int y){//散块for(int i=l;i<=r;i++){if(a[i]==x){bc[pos[i]][PP[x]]--;bc[pos[i]][PP[y]]++;c[pos[i]][x]--;c[pos[i]][y]++;a[i]=y;}}
}
inline void update(int l,int r,int x,int y){//处理每个块的数据可以差分处理if((x==y) || c[pos[r]][x]-c[pos[l]-1][x]==0 ){//x==y 或者是 区间内没有xreturn;}//差分单独处理这块的答案for(int i=t;i>=pos[l];i--){bc[i][PP[x]]-=bc[i-1][PP[x]];bc[i][PP[y]]-=bc[i-1][PP[y]];c[i][x]-=c[i-1][x];c[i][y]-=c[i-1][y];}int p=pos[l];int q=pos[r];if(p==q){pushdown(p);modify(l,r,x,y);//散块build(p);for(int i=p;i<=t;i++){bc[i][PP[x]]+=bc[i-1][PP[x]];bc[i][PP[y]]+=bc[i-1][PP[y]];c[i][x]+=c[i-1][x];c[i][y]+=c[i-1][y]; }}else{pushdown(p);modify(l,R[p],x,y);//散块build(p);pushdown(q);modify(L[q],r,x,y);//散块build(q);for(int i=p+1;i<=q-1;i++){if(!c[i][x]){//不存在x continue;}else if(c[i][y]){pushdown(i);modify(L[i],R[i],x,y);build(i); }else{bc[i][PP[y]]+=c[i][x];bc[i][PP[x]]-=c[i][x];c[i][y]+=c[i][x];c[i][x]=0;merge(i,x,y);}}for(int i=p;i<=t;i++){bc[i][PP[x]]+=bc[i-1][PP[x]];bc[i][PP[y]]+=bc[i-1][PP[y]];c[i][x]+=c[i-1][x];c[i][y]+=c[i-1][y]; }}
}
完整代码
#include<iostream>
#include<algorithm>
#include<cmath>
#include<vector>
#define INF (1ll<<60)
using namespace std;
typedef long long ll;
namespace Lan {inline string sread() {string s=" ";char e=getchar();while(e==' '||e=='\n')e=getchar();while(e!=' '&&e!='\n')s+=e,e=getchar();return s;}inline void swrite(string s){for(char e:s)putchar(e);printf("\n");}inline ll read() {ll x=0,y=1;char c=getchar();while(!isdigit(c)){if(c=='-')y=-1;c=getchar();}while(isdigit(c)){x=(x<<3)+(x<<1)+(c^48);c=getchar();}return x*=y;}inline void write(ll x) {if(x<0){x=-x,putchar('-');}ll sta[35],top=0;do sta[top++]=x%10,x/=10;while(x);while(top)putchar(sta[--top]+'0');}
}using namespace Lan;
const int N=1e5+9;
const int M=1e5+2;
const int B=5e2+9;
int a[N],bc[B][N],c[B][N];//块内的值域块中的数的数量,以及该数值的数
int L[B],R[B],pos[N];
int LL[B],RR[B],PP[N];
int index[B][N],val[B][N],loc[N];
int t1[B],t2[N];//散块查kth
int blo,t,tt;
inline void merge(int id,int x,int y){//合并index[id][y]=index[id][x];val[id][index[id][x]]=y;index[id][x]=0;
}
inline void pushdown(int id){//还原数组for(int i=L[id];i<=R[id];i++){a[i]=val[id][loc[i]];//}
}
inline void build(int id){//建立关系int cur=0;//根标号for(int i=1;i<=blo;i++){//清空关系index[id][val[id][i]]=0;}for(int i=L[id];i<=R[id];i++){if(!index[id][a[i]]){cur++;index[id][a[i]]=cur;val[id][cur]=a[i];}loc[i]=index[id][a[i]];}
}
inline void modify(int l,int r,int x,int y){//散块for(int i=l;i<=r;i++){if(a[i]==x){bc[pos[i]][PP[x]]--;bc[pos[i]][PP[y]]++;c[pos[i]][x]--;c[pos[i]][y]++;a[i]=y;}}
}
inline void update(int l,int r,int x,int y){//处理每个块的数据可以差分处理if((x==y) || c[pos[r]][x]-c[pos[l]-1][x]==0 ){//x==y 或者是 区间内没有xreturn;}//差分单独处理这块的答案for(int i=t;i>=pos[l];i--){bc[i][PP[x]]-=bc[i-1][PP[x]];bc[i][PP[y]]-=bc[i-1][PP[y]];c[i][x]-=c[i-1][x];c[i][y]-=c[i-1][y];}int p=pos[l];int q=pos[r];if(p==q){pushdown(p);modify(l,r,x,y);//散块build(p);for(int i=p;i<=t;i++){bc[i][PP[x]]+=bc[i-1][PP[x]];bc[i][PP[y]]+=bc[i-1][PP[y]];c[i][x]+=c[i-1][x];c[i][y]+=c[i-1][y]; }}else{pushdown(p);modify(l,R[p],x,y);//散块build(p);pushdown(q);modify(L[q],r,x,y);//散块build(q);for(int i=p+1;i<=q-1;i++){if(!c[i][x]){//不存在x continue;}else if(c[i][y]){pushdown(i);modify(L[i],R[i],x,y);build(i); }else{bc[i][PP[y]]+=c[i][x];bc[i][PP[x]]-=c[i][x];c[i][y]+=c[i][x];c[i][x]=0;merge(i,x,y);}}for(int i=p;i<=t;i++){bc[i][PP[x]]+=bc[i-1][PP[x]];bc[i][PP[y]]+=bc[i-1][PP[y]];c[i][x]+=c[i-1][x];c[i][y]+=c[i-1][y]; }}
}
inline int kth(int l,int r,int k){int p=pos[l];int q=pos[r];ll res=0;ll sum=0;if(p==q){pushdown(p);for(int i=l;i<=r;i++){t2[i]=a[i];}nth_element(t2+l,t2+l+k-1,t2+r+1);res=t2[l+k-1];//0->kfor(int i=l;i<=r;i++){t2[i]=0;//清空}}else{//散块计数pushdown(p);for(int i=l;i<=R[p];i++){t1[PP[a[i]]]++;t2[a[i]]++;}pushdown(q);for(int i=L[q];i<=r;i++){t1[PP[a[i]]]++;t2[a[i]]++;}//整块处理for(int i=1;i<=tt;i++){//枚举值域块if(sum+t1[i]+bc[q-1][i]-bc[p][i]>=k){//[p+1,q-1]for(int j=LL[i];j<=RR[i];j++){//枚举块内值域if(sum+t2[j]+c[q-1][j]-c[p][j]>=k){//找到位置//查询结束前清空for(int o=l;o<=R[p];o++){//[l,R[p]]t1[PP[a[o]]]--;t2[a[o]]--;}for(int o=L[q];o<=r;o++){//[R[q],r]t1[PP[a[o]]]--;t2[a[o]]--;}//返回答案return j;//kth}else{sum+=(t2[j]+c[q-1][j]-c[p][j]);}}}else{sum+=(t1[i]+bc[q-1][i]-bc[p][i]);//累加}}}return res;
}
int main(){// ios::sync_with_stdio(false);// cin.tie(0),cout.tie(0);int n,m;n=read(),m=read();for(int i=1;i<=n;i++){a[i]=read();}//分块blo=sqrt(n);t=sqrt(n);for(int i=1;i<=t;i++){L[i]=(i-1)*t+1;R[i]=i*t;}if(R[t]<n){t++;L[t]=R[t-1]+1;R[t]=n;}for(int i=1;i<=t;i++){for(int j=L[i];j<=R[i];j++){pos[j]=i;}}//值域分块tt=sqrt(M);for(int i=1;i<=tt;i++){LL[i]=(i-1)*tt+1;RR[i]=i*tt;}if(RR[tt]<M){tt++;LL[tt]=RR[tt-1]+1;RR[tt]=M;}for(int i=1;i<=tt;i++){for(int j=LL[i];j<=RR[i];j++){PP[j]=i;}}//预处理//值域分块for(int i=1;i<=t;i++){for(int j=1;j<=t+100;j++){bc[i][j]=bc[i-1][j];//块继承}for(int j=1;j<M;j++){c[i][j]=c[i-1][j];//数量继承}for(int j=L[i];j<=R[i];j++){bc[i][PP[a[j]]]++;//继续记录c[i][a[j]]++;}}//并查集for(int i=1;i<=t;i++){build(i);}for(int i=1;i<=m;i++){int op;op=read();int l,r;l=read(),r=read();if(op==1){int x,y;x=read(),y=read();update(l,r,x,y);}else{int k;k=read();cout<<kth(l,r,k)<<'\n';}}return 0;
}
学会了值域分块求k值,学完第二分块并查集,感觉第一分块的操作一并查集那块挺好写的
差分修改还是很妙的
下一个大分块 maybe 天降之物?