P3730 莫队+值域分块题解
DaydreamWarrior · · 题解
莫队+值域分块题解
题意简述
查询区间内出现次数第k小的值的出现次数。
题目分析
这种
考虑平衡复杂度,莫队有
一些细节
- 题目值域是
[1,10^9] ,需要离散化。 - 莫队的最优块长其实是
\dfrac{n}{\sqrt m} 。因为n 和m 同价的时候和\sqrt n 是一样的,一般也没理这个事。证明详见 普通莫队算法 - Oi Wiki。
代码
#include <iostream>
#include <vector>
#include <cmath>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 100005,M = 320;
class inquiry{
public:
int l,r,x,block,id;
inline bool operator < (const inquiry &tmp)const{
if(block!=tmp.block)//奇偶性优化,个人感觉不是对时间优化,而是防出题人卡莫队
return l<tmp.l;
return block&1?r<tmp.r:r>tmp.r;
}
};
vector<inquiry> qus;
int ans[N],cnt[N];
vector<int> has;
int block,len;
int a[N];
int n,m;
namespace sqrtrange{//值域分块
int cnt[N],sum[M];
inline void update(int x,int c){
cnt[x] += c;
sum[x/len] += c;//更新所在块的贡献
}
inline int query(int x){
int top = ceil(n*1.0/len);
for(int k=0;k<top;k++){
if(x>sum[k])//答案不在块内
x -= sum[k];
else{//答案在块内,遍历整个块寻找答案
int l = k*len,r = min((k+1)*len,n);
for(int j=l;j<=r;j++){
if(x>cnt[j])
x -= cnt[j];
else
return j;
}
}
}
return -1;
}
}
inline int h(int x){
return lower_bound(has.begin(),has.end(),x)-has.begin();
}
inline void add(int x){
if(cnt[a[x]])
sqrtrange::update(cnt[a[x]],-1);//减去修改前出现次数的贡献
cnt[a[x]]++;
sqrtrange::update(cnt[a[x]],1);//更新贡献
}
inline void del(int x){
sqrtrange::update(cnt[a[x]],-1);
cnt[a[x]]--;
if(cnt[a[x]])
sqrtrange::update(cnt[a[x]],1);
}
int main(){
n = in(),m = in();
block = ceil(n/sqrt(m));
for(int k=1;k<=n;k++)
has.emplace_back(a[k]=in());
sort(has.begin(),has.end());
has.erase(unique(has.begin(),has.end()),has.end());
for(int k=1;k<=n;k++)
a[k] = h(a[k]);
len = ceil(sqrt(n));
for(int k=1;k<=m;k++){
int l = in(),r = in(),x = in();
qus.push_back({l,r,x,l/block,k});
}
sort(qus.begin(),qus.end());
int L = 1,R = 0;
for(auto[l,r,x,tmp,id]:qus){
while(l<L)
add(--L);
while(l>L)
del(L++);
while(r<R)
del(R--);
while(r>R)
add(++R);
ans[id] = sqrtrange::query(x);
}
for(int k=1;k<=m;k++){
out(ans[k]);
putchar('\n');
}
return 0;
}
后话
- 这种莫队套分块去平衡复杂度还挺常见的,有时候也搭配
bitset。 - 第一次写题解,水平有限还请见谅。