它来了!
分析一下第一个操作,不是写过嘛,并查集
分析一下第二个操作,二分套二分答案
拿下了这题
仔细分析,貌似时间复杂度是错的
我们考虑块套块 时间复杂度
对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->k
for(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 或者是 区间内没有x
return;
}
//差分单独处理这块的答案
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 或者是 区间内没有x
return;
}
//差分单独处理这块的答案
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->k
for(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 天降之物?