对题EF,这里提供一种根号平衡的算法,我们知道莫队的板子小Z的袜子上,是使用莫队维护区间每个数的数量,而我们又发现中位数,本质上是查询一个区间中第k位的值[k=(r-l+1)向上取整]。于是我们通过莫队已经可以得到每个区间的每个的数量,那么只要找到一个快速查询的方法即可。
那么此时问题就变成,如何在i~n的坐标索引中如何找到cnt的和,为(r-l+1)/2向上取整处的索引。我们发现,这个需求可以靠分块很好的实现[只需要掌握小z的袜子,和使用分块通过线段树1即可]。最后,我们分析复杂度,对于莫队而言,维护一个区间上的cnt[N]的单次的时间复杂度是sqrt(n);而对于被维护好了的cnt数组,做分块查询的时间复杂度也是sqrt(n)。
这个操作便是根号平衡。即我们的分块查询操作,不会增加莫队的时间复杂度;同时莫队维护的cnt数组又可以作为切合当前区间的素材,给分块做查询。最后,不要忘记离散化,代码如下:
#pragma GCC optimize(2)
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
#define AII array<int,3>
/*
根号平衡。
莫队维护每个区间状态下,每个数的数量,分块查询第k小。
*/
const int N=1e5+10;
int bsiz_mo,bcnt_mo,bel_mo[N];
int bsiz_fk,bcnt_fk,bel_fk[N],bl_fk[N],br_fk[N],cnt_fk[N],s_cnt_fk[N];
inline void init_fk(int n){
bsiz_fk=sqrt(n);
bcnt_fk=(bsiz_fk-1+n)/bsiz_fk;
for(int b=1;b<=bcnt_fk;b++){
bl_fk[b]=(b-1)*bsiz_fk,br_fk[b]=min(n,b*bsiz_fk);
for(int s=bl_fk[b];s<=br_fk[b];s++) bel_fk[s]=b;
}
}
inline void init_mo(int n){
bsiz_mo=sqrt(n);
bcnt_mo=(bsiz_mo-1+n)/bsiz_mo;
for(int b=1;b<=bcnt_mo;b++){
for(int s=(b-1)*bsiz_mo;s<=min(n,b*bsiz_mo);s++) bel_mo[s]=b;
}
}
int main(){
ios::sync_with_stdio(false);
cin.tie(nullptr);
int n,q;cin>>n>>q;
init_mo(n);init_fk(N-5);
int a[n+1],b[n+1],c[n+1];
for(int i=1;i<=n;i++) {
cin>>a[i];b[i]=a[i];
}sort(b+1,b+n+1);
int ln=unique(b+1,b+n+1)-(b+1);
for(int i=1;i<=n;i++){
c[i]=lower_bound(b+1,b+ln+1,a[i])-b;
}vector<AII> ask(q+1);
for(int i=1;i<=q;i++){
auto &[l,r,j]=ask[i];
cin>>l>>r;j=i;
}sort(ask.begin()+1,ask.end(),[&](AII x,AII y){
if(bel_mo[x[0]]^bel_mo[y[0]]) return bel_mo[x[0]]<bel_mo[y[0]];
if(bel_mo[x[0]]%2==0) return bel_mo[x[1]]>bel_mo[y[1]];
return bel_mo[x[1]]<bel_mo[y[1]];
});vector<int> ans(q+1);
auto add=[&](int id)->void{
cnt_fk[c[id]]++;s_cnt_fk[bel_fk[c[id]]]++;
};
auto del=[&](int id)->void{
cnt_fk[c[id]]--;s_cnt_fk[bel_fk[c[id]]]--;
};
auto qry=[&](int l,int r)->int{
int k=(r-l+2)/2;
for(int b=1;b<=bcnt_fk;b++){
if(k>s_cnt_fk[b]) k-=s_cnt_fk[b];
else{
for(int s=bl_fk[b];s<=br_fk[b];s++){
if(k>cnt_fk[s]) k-=cnt_fk[s];
else return s;
}
}
}return -1;
};int tl=1,tr=0;
for(int i=1;i<=q;i++){
auto [l,r,j]=ask[i];
while(tl>l) add(--tl);
while(tr<r) add(++tr);
while(tl<l) del(tl++);
while(tr>r) del(tr--);
ans[j]=b[qry(l,r)];
}
for(int i=1;i<=q;i++){
cout<<ans[i]<<"\n";
}
return 0;
}
其实线段树合并也可以过,就是把主席树的合并拆在外面。
#pragma GCC optimize(2)
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=1e5+10;
const int INF=1e9;
#define ls(x) tre[x].ls
#define rs(x) tre[x].rs
#define cnt(x) tre[x].cnt
struct STR{
int ls,rs,cnt;
}tre[N*40];
int idx,rt[N],a[N];
inline void pushup(int u){
cnt(u)=cnt(ls(u))+cnt(rs(u));
}
inline void update(int &u,int tl,int tr,int id){
if(!u) u=++idx;
if(tl==tr){
cnt(u)++;return;
}int mid=(tl+tr)>>1;
if(id<=mid) update(ls(u),tl,mid,id);
else update(rs(u),mid+1,tr,id);
pushup(u);
}
inline void merge(int &u,int v){
if(u && v){
cnt(u)+=cnt(v);
merge(ls(u),ls(v));
merge(rs(u),rs(v));
}
else u|=v;
}
inline int kth(int u,int v,int tl,int tr,int k){
if(tl==tr) return k>cnt(u)-cnt(v) ? -1 : tl;
int mid=(tl+tr)>>1;
if(k<=cnt(ls(u))-cnt(ls(v))) return kth(ls(u),ls(v),tl,mid,k);
return kth(rs(u),rs(v),mid+1,tr,k-cnt(ls(u))+cnt(ls(v)));
}
int main(){
ios::sync_with_stdio(false);
cin.tie(nullptr);
int n,m;cin>>n>>m;int b[n+1];
for(int i=1;i<=n;i++) cin>>a[i];
for(int i=1;i<=n;i++) b[i]=a[i];
sort(b+1,b+n+1);int ln=unique(b+1,b+n+1)-(b+1);
for(int i=1;i<=n;i++){
a[i]=lower_bound(b+1,b+ln+1,a[i])-b;
}
for(int i=1;i<=n;i++){
update(rt[i],1,ln,a[i]);
}
for(int i=2;i<=n;i++){
merge(rt[i],rt[i-1]);
}
while(m--){
int l,r;cin>>l>>r;
int k=(r-l)/2+1;
int id=kth(rt[r],rt[l-1],1,ln,k);
cout<<b[id]<<"\n";
}
return 0;
}