一道数据结构好题,不看题解之前能想出来思路对自己的数据结构提升水平会大大提升。
由于打这个题之前打过一遍 Treap,又看到是一个只有查询的题,又看到了最值 h h h。
因此第一感是个:
莫队+Treap+二分( O ( n n log n ) O(n\sqrt{n}\log n) O(nnlogn))。
氧化钙,好牛马的复杂度,空间也很假,还不好实现。。。
我小心翼翼打开了题解区,值域分块+莫队。大悟的感觉。
然后打了很久。发现过不了样例,跟 @Others 神犇的题解对比了一下找了一下问题,一会儿就调出来了。
下次一定不ctj
很显然,一眼莫队。
这个序列给的就是次数,所以说不用想就知道莫队维护的肯定也是次数。(数据范围也给了的)
根据以前打莫队的经验。维护一个次数的桶。
好,板子对吧,打完了,然后呢?怎么维护答案呢?
明显直接枚举便失去了时间复杂度优势,考虑加入值域分块。
不懂值域分块的可以百度一下,就是分块维护的是值域。
值域分块明显维护的便是次数的一个值域,块长取根号即可。
然后我们枚举的时候直接枚举从大到小每个值域分块的块,那么这个块满足条件的情况是什么呢?
我们假设这个块值域的最左端点为 L i L_i Li,这个块的值是 c n t i cnt_i cnti,以前已经有 s s s 这么多块内值了。
那么只有满足:
L i ≤ s + c n t i L_i\le s+cnt_i Li≤s+cnti
(这个看着有点雾,画一下样例应该能懂,也就相当于把题面的式子换了一下)。
答案才会在这个块里,然后如果满足,直接在块内暴力查找即可,就不用细讲了,原理和上述差不多。
总的复杂度显然是 O ( n n ) O(n\sqrt{n}) O(nn) 的。
当然有哪里写得有问题或者没有怎么看懂的,也可以私信我。
#include
using namespace std;
const int N=2e5+5;
const int M=2e5+5;
const int K=2e5+5;
int n,m,lenn,lenk;
int a[N],idx[N],idk[K];
int cnt[1005],t[K],kl[1005],kr[1005];
int ans[M];
struct Q{
int l,r,k;
}q[M];
bool cmp(Q x,Q y){
return idx[x.l]==idx[y.l]?x.r<y.r:idx[x.l]<idx[y.l];
}
void add(int x){
cnt[idk[x]]++;
t[x]++;
}
void sub(int x){
cnt[idk[x]]--;
t[x]--;
}
int Ans(){
int res=0;
for(int i=idk[200000];i>=1;i--){
if(res+cnt[i]>=kl[i]){
for(int j=kr[i];j>=kl[i];j--){
if(res+t[j]>=j) return j;
res+=t[j];
}
}
res+=cnt[i];
}
return res;
}
int main(){
scanf("%d %d",&n,&m);
lenn=sqrt(n);
lenk=sqrt(200000);
for(int i=1;i<=n;i++) scanf("%d",&a[i]);
for(int i=1;i<=m;i++) scanf("%d %d",&q[i].l,&q[i].r),q[i].k=i,idx[i]=(i+lenn-1)/lenn;
for(int i=1;i<=200000;i++) idk[i]=(i+lenk-1)/lenk;
for(int i=1;i<=200000;i++) if(!kl[idk[i]]) kl[idk[i]]=i;
for(int i=200000;i>=1;i--) if(!kr[idk[i]]) kr[idk[i]]=i;
sort(q+1,q+1+m,cmp);
int l=1,r=0;
for(int i=1;i<=m;i++){
while(q[i].l<l) add(a[--l]);
while(q[i].l>l) sub(a[l++]);
while(q[i].r<r) sub(a[r--]);
while(q[i].r>r) add(a[++r]);
ans[q[i].k]=Ans();
}
for(int i=1;i<=m;i++) printf("%d\n",ans[i]);
return 0;
}