数列分块
概述
区间问题一般都十分灵活,可以用线段树来解决,虽然时间复杂度可以达到 O ( l o g ( n ) ) O(log(n)) O(log(n)),但是有时候不太好写,分块可以让时间复杂度达到 O ( n ) O(\sqrt n) O(n),虽然不及线段树,但是应用范围也很广,也比较简单,很有学习的必要
- 分块思想很简单,一般把一个区间分成
n
\sqrt n
n块,如下图,如果有剩余部分,这样我们就多加一块
- 然后比如说处理一个区间
[
L
,
R
]
[L,R]
[L,R],如果是这样
- 那么我们对于 [ L , 2 ] [L,2] [L,2]和 [ 4 , R ] [4,R] [4,R]这两块暴力处理,中间两个大块的区间整体处理,这就是分块的思想,那么就介绍完了,具体实现过程见例题
数列分块九题
https://loj.ac/p?keyword=%E6%95%B0%E5%88%97%E5%88%86%E5%9D%97
1. 区间加,单点查询
- 设每块的大小为 b l o c k block block,那么块数应该是 ⌈ n b l o c k ⌉ \lceil \frac{n}{block}\rceil ⌈blockn⌉,然后我们使用 b e l o n g [ i ] belong[i] belong[i]记录 i i i节点在哪个块中, L [ i ] , R [ i ] L[i],R[i] L[i],R[i]分别表示 i i i块左端点和右端点位置,具体见代码,思路很清晰,这也是树状数组和线段树的入门题
- 更新的时候注意需要检查是不是在一个块中,如果在一个块中那么直接暴力更新,否则两端暴力,中间完整部分按照块更新
- 注意右端点要和 n n n取一个最小值,因为右端点可能越界,主要是最后一块的问题
#include <bits/stdc++.h>
using namespace std;
int main(){
ios::sync_with_stdio(false);
cin.tie(0);
int n;
cin >> n;
vector<int> a(n + 1), belong(n + 1);
int block = sqrt(n);
int tot = n / block;
if(n % block) tot += 1;
for(int i=1;i<=n;i++){
cin >> a[i];
belong[i] = (i - 1) / block + 1;
}
vector<int> L(tot + 1), R(tot + 1), lazy(tot + 1);
for(int i=1;i<=tot;i++){
L[i] = (i - 1) * block + 1;
R[i] = min(n, i * block);
}
for(int i=0;i<n;i++){
int opt, l, r, c;
cin >> opt >> l >> r >> c;
if(opt == 0){
function<void(int, int, int)> modify = [&](int l, int r, int c){
if(belong[l] == belong[r]){
for(int i=l;i<=r;i++){
a[i] += c;
}
}else{
for(int i=l;i<=R[belong[l]];i++){
a[i] += c;
}
for(int i=L[belong[r]];i<=r;i++){
a[i] += c;
}
for(int i=belong[l]+1;i<=belong[r]-1;i++){
lazy[i] += c;
}
}
};
modify(l, r, c);
}else{
function<int(int)> query = [&](int r){
return a[r] + lazy[belong[r]];
};
cout << query(r) << '\n';
}
}
return 0;
}
2. 区间加,区间小于某个数的数的个数
- 这也是一个类型题,区间加我们可以暴力,那么怎么统计区间小于某个数的个数呢?处理方法是使用另外一个数组,维护区间的单调性,然后在这个数组里面二分找小于 c c c的数的个数,看起来好像很慢,每一次更新都要把暴力修改过的块赋值给 d d d数组,而且每次都要排个序,但是其实挺快的,时间复杂度 n n n\sqrt n nn,这题数据 5 e 4 5e4 5e4,没问题,可能 1 e 6 1e6 1e6就不太行了
- 二分手写吧,很好写
#include <bits/stdc++.h>
using namespace std;
int main(){
ios::sync_with_stdio(false);
cin.tie(0);
int n;
cin >> n;
int block = sqrt(n);
int tot = n / block;
if(n % block) tot += 1;
vector<int> a(n + 1), belong(n + 1), d(n + 1);
for(int i=1;i<=n;i++){
cin >> a[i];
d[i] = a[i];
belong[i] = (i - 1) / block + 1;
}
vector<int> L(tot + 1), R(tot + 1), lazy(tot + 1);
for(int i=1;i<=tot;i++){
L[i] = (i - 1) * block + 1;
R[i] = min(n, i * block);
sort(d.begin() + L[i], d.begin() + R[i] + 1);
}
for(int i=0;i<n;i++){
int opt, l, r, c;
cin >> opt >> l >> r >> c;
if(opt == 0){
function<void(int, int, int)> modify = [&](int l, int r, int c){
if(belong[l] == belong[r]){
for(int i=l;i<=r;i++){
a[i] += c;
}
for(int i=L[belong[l]];i<=R[belong[l]];i++){
d[i] = a[i];
}sort(d.begin() + L[belong[l]], d.begin() + R[belong[l]] + 1);
}else{
for(int i=l;i<=R[belong[l]];i++){
a[i] += c;
}
for(int i=L[belong[l]];i<=R[belong[l]];i++){
d[i] = a[i];
}sort(d.begin() + L[belong[l]], d.begin() + R[belong[l]] + 1);
for(int i=L[belong[r]];i<=r;i++){
a[i] += c;
}
for(int i=L[belong[r]];i<=R[belong[r]];i++){
d[i] = a[i];
}sort(d.begin() + L[belong[r]], d.begin() + R[belong[r]] + 1);
for(int i=belong[l]+1;i<=belong[r]-1;i++){
lazy[i] += c;
}
}
};
modify(l, r, c);
}else{
function<int(int, int, int)> query = [&](int l, int r, int c){
int ans = 0;
if(belong[l] == belong[r]){
for(int i=l;i<=r;i++){
if(a[i] + lazy[belong[i]] < c) ans += 1;
}
}else{
for(int i=l;i<=R[belong[l]];i++){
if(a[i] + lazy[belong[i]] < c) ans += 1;
}
for(int i=L[belong[r]];i<=r;i++){
if(a[i] + lazy[belong[i]] < c) ans += 1;
}
for(int i=belong[l]+1;i<=belong[r]-1;i++){
int x = L[i];
int y = R[i];
int res = 0;
while(x <= y){
int mid = (y - x >> 1) + x;
if(d[mid] + lazy[i] < c){
res = mid + 1 - L[i];
x = mid + 1;
}else{
y = mid - 1;
}
}
ans += res;
}
}
return ans;
};
cout << query(l, r, c * c) << '\n';
}
}
return 0;
}
3. 区间加,求前驱
- 上一次求前驱还是在学 t r e a p treap treap树的时候,虽然现在忘得差不多了,洛谷上好像有道二叉树的题也是求前驱等等这一套东西的,求前驱的方法很多,线段树、平衡树、普通二叉树都能做,现在我们试试使用分块来解决这个问题
- 某个数的前驱就是小于这个数的最大值,那么我们仍然用第二题的思路,用一个有序数组,然后对于一整块的元素,在这个数组里面二分查找,两侧小块部分直接暴力,如果都找不到就返回 − 1 -1 −1,注意 l a z y lazy lazy标记别忘了
#include <bits/stdc++.h>
using namespace std;
int main(){
ios::sync_with_stdio(false);
cin.tie(0);
int n;
cin >> n;
int block = sqrt(n);
int tot = n / block;
if(n % block) tot += 1;
vector<int> a(n + 1), belong(n + 1), d(n + 1);
for(int i=1;i<=n;i++){
cin >> a[i];
d[i] = a[i];
belong[i] = (i - 1) / block + 1;
}
vector<int> L(tot + 1), R(tot + 1), lazy(tot + 1);
for(int i=1;i<=tot;i++){
L[i] = (i - 1) * block + 1;
R[i] = min(n, i * block);
sort(d.begin() + L[i], d.begin() + R[i] + 1);
}
for(int i=0;i<n;i++){
int opt, l, r, c;
cin >> opt >> l >> r >> c;
if(opt == 0){
function<void(int, int, int)> modify = [&](int l, int r, int c){
if(belong[l] == belong[r]){
for(int i=l;i<=r;i++){
a[i] += c;
}
for(int i=L[belong[l]];i<=R[belong[l]];i++){
d[i] = a[i];
}sort(d.begin() + L[belong[l]], d.begin() + R[belong[l]] + 1);
}else{
for(int i=l;i<=R[belong[l]];i++){
a[i] += c;
}
for(int i=L[belong[l]];i<=R[belong[l]];i++){
d[i] = a[i];
}sort(d.begin() + L[belong[l]], d.begin() + R[belong[l]] + 1);
for(int i=L[belong[r]];i<=r;i++){
a[i] += c;
}
for(int i=L[belong[r]];i<=R[belong[r]];i++){
d[i] = a[i];
}sort(d.begin() + L[belong[r]], d.begin() + R[belong[r]] + 1);
for(int i=belong[l]+1;i<=belong[r]-1;i++){
lazy[i] += c;
}
}
};
modify(l, r, c);
}else{
function<int(int, int, int)> query = [&](int l, int r, int c){
int ans = INT_MIN;
if(belong[l] == belong[r]){
for(int i=l;i<=r;i++){
if(a[i] + lazy[belong[i]] < c){
ans = max(a[i] + lazy[belong[i]], ans);
}
}
}else{
for(int i=l;i<=R[belong[l]];i++){
if(a[i] + lazy[belong[i]] < c){
ans = max(ans, a[i] + lazy[belong[i]]);
}
}
for(int i=L[belong[r]];i<=r;i++){
if(a[i] + lazy[belong[i]] < c){
ans = max(ans, a[i] + lazy[belong[i]]);
}
}
for(int i=belong[l]+1;i<=belong[r]-1;i++){
int x = L[i];
int y = R[i];
while(x <= y){
int mid = (y - x >> 1) + x;
if(d[mid] + lazy[i] < c){
ans = max(ans, d[mid] + lazy[i]);
x = mid + 1;
}else{
y = mid - 1;
}
}
}
}
return (ans == INT_MIN ? -1 : ans);
};
cout << query(l, r, c) << '\n';
}
}
return 0;
}
4. 区间加,区间求和
- 用一个数组维护块内和,更新的时候同时更新这个数组,思路简单,细节需要注意,且需要开long long
#include <bits/stdc++.h>
using namespace std;
#define int long long
signed main(){
ios::sync_with_stdio(false);
cin.tie(0);
int n;
cin >> n;
vector<int> a(n + 1), belong(n + 1);
int block = sqrt(n);
int tot = n / block;
if(n % block) tot += 1;
vector<int> L(tot + 1), R(tot + 1), sum(tot + 1), lazy(tot + 1);
for(int i=1;i<=n;i++){
cin >> a[i];
belong[i] = (i - 1) / block + 1;
sum[belong[i]] += a[i];
}
for(int i=1;i<=tot;i++){
L[i] = (i - 1) * block + 1;
R[i] = min(i * block, n);
}
for(int i=0;i<n;i++){
int opt, l, r, c;
cin >> opt >> l >> r >> c;
if(opt == 0){
function<void(int, int, int)> modify = [&](int l, int r, int c){
if(belong[l] == belong[r]){
for(int i=l;i<=r;i++){
a[i] += c;
sum[belong[i]] += c;
}
}else{
for(int i=l;i<=R[belong[l]];i++){
a[i] += c;
sum[belong[i]] += c;
}
for(int i=L[belong[r]];i<=r;i++){
a[i] += c;
sum[belong[i]] += c;
}
for(int i=belong[l]+1;i<=belong[r]-1;i++){
sum[i] += (R[i] - L[i] + 1) * c;
lazy[i] += c;
}
}
};
modify(l, r, c);
}else{
function<int(int, int, int)> query = [&](int l, int r, int c){
int ans = 0;
if(belong[l] == belong[r]){
for(int i=l;i<=r;i++){
ans += a[i];
if(ans >= c) ans %= c;
ans += lazy[belong[i]];
if(ans >= c) ans %= c;
}
}else{
for(int i=l;i<=R[belong[l]];i++){
ans += a[i];
if(ans >= c) ans %= c;
ans += lazy[belong[i]];
if(ans >= c) ans %= c;
}
for(int i=L[belong[r]];i<=r;i++){
ans += a[i];
if(ans >= c) ans %= c;
ans += lazy[belong[i]];
if(ans >= c) ans %= c;
}
for(int i=belong[l]+1;i<=belong[r]-1;i++){
ans += sum[i];
if(ans >= c) ans %= c;
}
}
return ans;
};
cout << query(l, r, c + 1) << '\n';
}
}
return 0;
}
5. 区间开方,区间查询
- 区间开方的的一个思路是区间开方的总次数会很少,因为一直开方取整下去不久就会变1, 1 e 9 1e9 1e9不停开方五次就会变1;如果原来是0那么不变,所以可以考虑维护一个区间最值,分块处理就是维护每个块的最值,如果发现块最值是 0 0 0或者 1 1 1,那么这个块就不用再开方了,因为不会再改变
- 然后再维护一个块内和,后面的处理比较简单,关键在于开方维护这里,具体见代码
#include <bits/stdc++.h>
using namespace std;
int main(){
ios::sync_with_stdio(false);
cin.tie(0);
int n;
cin >> n;
int block = sqrt(n);
int tot = n / block;
if(n % block) tot += 1;
vector<int> a(n + 1), belong(n + 1);
vector<int> maxn(tot + 1), sum(tot + 1);
for(int i=1;i<=n;i++){
cin >> a[i];
belong[i] = (i - 1) / block + 1;
sum[belong[i]] += a[i];
maxn[belong[i]] = max(maxn[belong[i]], a[i]);
}
vector<int> L(tot + 1), R(tot + 1);
for(int i=1;i<=tot;i++){
L[i] = (i - 1) * block + 1;
R[i] = min(n, i * block);
}
for(int k=0;k<n;k++){
int opt, l, r, c;
cin >> opt >> l >> r >> c;
if(opt == 0){
function<void(int, int)> modify = [&](int l, int r){
if(belong[l] == belong[r]){
if(maxn[belong[l]] == 0 || maxn[belong[l]] == 1) return;
for(int i=l;i<=r;i++){
int past = a[i];
a[i] = sqrt(a[i]);
sum[belong[i]] -= past - a[i];
}
int mx = 0;
for(int i=L[belong[l]];i<=R[belong[l]];i++){
mx = max(mx, a[i]);
}
maxn[belong[l]] = mx;
}else{
if(maxn[belong[l]] != 0 && maxn[belong[l]] != 1){
for(int i=l;i<=R[belong[l]];i++){
int past = a[i];
a[i] = sqrt(a[i]);
sum[belong[i]] -= past - a[i];
}
int mx = 0;
for(int i=L[belong[l]];i<=R[belong[l]];i++){
mx = max(mx, a[i]);
}
maxn[belong[l]] = mx;
}
if(maxn[belong[r]] != 0 && maxn[belong[r]] != 1){
for(int i=L[belong[r]];i<=r;i++){
int past = a[i];
a[i] = sqrt(a[i]);
sum[belong[i]] -= past - a[i];
}
int mx = 0;
for(int i=L[belong[r]];i<=R[belong[r]];i++){
mx = max(mx, a[i]);
}
maxn[belong[r]] = mx;
}
for(int i=belong[l]+1;i<=belong[r]-1;i++){
if(maxn[i] == 0 || maxn[i] == 1) continue;
int mx = 0;
for(int j=L[i];j<=R[i];j++){
int past = a[j];
a[j] = sqrt(a[j]);
sum[i] -= past - a[j];
mx = max(mx, a[j]);
}
maxn[i] = mx;
}
}
};
modify(l, r);
}else{
function<int(int, int)> query = [&](int l, int r){
int ans = 0;
if(belong[l] == belong[r]){
for(int i=l;i<=r;i++){
ans += a[i];
}
}else{
for(int i=l;i<=R[belong[l]];i++){
ans += a[i];
}
for(int i=L[belong[r]];i<=r;i++){
ans += a[i];
}
for(int i=belong[l]+1;i<=belong[r]-1;i++){
ans += sum[i];
}
}
return ans;
};
cout << query(l, r) << '\n';
}
}
return 0;
}
6. 单点插入,单点询问
- 这个操作使用平衡树最简单。使用分块思路是设置每个块类型为vector,每次暴力插入,对吧超级暴力,但是这会带来一个问题,如果有某一块插入了过多的元素,那么分块就会退化成更加普通的暴力,解决这个问题的办法是对块的大小进行限制,如果超过了预定的大小,那么就重新分块,这个大小可以乱搞,但是比较理性的大小选择是 2 n \sqrt{2n} 2n,其中 n n n表示元素的数量,注意这里 n n n的大小是变化之中的
- 这样分块的效果是,插入时间复杂度是 O ( n ) O(\sqrt{n}) O(n),有 n n n次插入,某一个块插入次数为 n \sqrt{n} n的时候就进行重新排布,重新分块的时间复杂度是 O ( n ) O(n) O(n)的,这样均摊时间复杂度是 O ( n n ) O(n\sqrt{n}) O(nn)
未经过重新分块程序如下
#include <bits/stdc++.h>
using namespace std;
int main(){
ios::sync_with_stdio(false);
cin.tie(0);
int n;
cin >> n;
int block = sqrt(n);
int tot = n / block;
if(n % block) tot += 1;
vector<vector<int> > b(tot + 1);
vector<int> belong(n + 1);
for(int i=1;i<=n;i++){
int x;
cin >> x;
belong[i] = (i - 1) / block + 1;
b[belong[i]].push_back(x);
}
int q = n;
while(q--){
int opt, l, r, c;
cin >> opt >> l >> r >> c;
if(opt == 0){
function<void(int, int)> modify = [&](int l, int r){
int pt = 1;
int sz = b[pt].size();
while(sz < l){
pt += 1;
sz += b[pt].size();
}
b[pt].insert(b[pt].begin() + l - (sz - b[pt].size()) - 1, r);
};
modify(l, r);
}else{
function<int(int)> query = [&](int r){
int pt = 1;
int sz = b[pt].size();
while(sz < r){
pt += 1;
sz += b[pt].size();
}
return b[pt][r - (sz - b[pt].size()) - 1];
};
cout << query(r) << '\n';
}
}
return 0;
}
重新分块程序如下
#include <bits/stdc++.h>
using namespace std;
int main(){
ios::sync_with_stdio(false);
cin.tie(0);
int n;
cin >> n;
int block = sqrt(n);
int tot = n / block;
if(n % block) tot += 1;
vector<vector<int> > b(tot + 1);
vector<int> belong(n + 1);
for(int i=1;i<=n;i++){
int x;
cin >> x;
belong[i] = (i - 1) / block + 1;
b[belong[i]].push_back(x);
}
int q = n;
function<void()> Rebuild = [&](){
block = sqrt(n);
vector<int> a(n + 1);
int top = 0;
for(int i=1;i<=tot;i++){
for(auto j : b[i]){
a[++top] = j;
}
b[i].clear();
}
tot = n / block;
if(n % block) tot += 1;
belong.resize(top + 1);
b.resize(tot + 1);
for(int i=1;i<=top;i++){
belong[i] = (i - 1) / block + 1;
b[belong[i]].push_back(a[i]);
}
};
while(q--){
int opt, l, r, c;
cin >> opt >> l >> r >> c;
if(opt == 0){
function<void(int, int)> modify = [&](int l, int r){
int pt = 1;
int sz = b[pt].size();
while(sz < l){
pt += 1;
sz += b[pt].size();
}
b[pt].insert(b[pt].begin() + l - (sz - b[pt].size()) - 1, r);
n += 1;
if(b[pt].size() > 2 * block) Rebuild();
};
modify(l, r);
}else{
function<int(int)> query = [&](int r){
int pt = 1;
int sz = b[pt].size();
while(sz < r){
pt += 1;
sz += b[pt].size();
}
return b[pt][r - (sz - b[pt].size()) - 1];
};
cout << query(r) << '\n';
}
}
return 0;
}
- 从时间上看好像还变慢了,但是这样时间复杂度是对的,可能换用静态数组能更快一些
7. 区间乘法,区间加法,单点询问
- 这也是一个高级数据结构的入门操作,加法我们之前已经处理过了,那么如果加法和乘法混合该怎么办呢?注意我们是单点询问,所以lazy标记是必须要有的,所以这里面我们需要两个lazy标记,分别表示加法和乘法
- 这两种操作我们都需要考虑整块和非整块这两种情况,对于整块,如果是一个加法操作,那么我们直接加到addlazy上面即可,如果是一个乘法操作,我们不仅需要乘到mullazy上面,我们还需要乘到addlazy上面;对于非整块,我们肯定是要暴力更新,在这之前我们必须先把这一块的lazy标记都传递下来,先乘后加,因为加法的lazy标记我们已经乘法处理过了
#include <bits/stdc++.h>
using namespace std;
const int MOD = 1e4 + 7;
int main(){
ios::sync_with_stdio(false);
cin.tie(0);
int n;
cin >> n;
int block = sqrt(n);
int tot = n / block;
if(n % block) tot += 1;
vector<int> a(n + 1);
vector<int> belong(n + 1);
for(int i=1;i<=n;i++){
cin >> a[i];
belong[i] = (i - 1) / block + 1;
}
vector<int> L(tot + 1), R(tot + 1);
vector<int> addlazy(tot + 1), mulazy(tot + 1, 1);
for(int i=1;i<=tot;i++){
L[i] = (i - 1) * block + 1;
R[i] = min(n, i * block);
}
for(int i=0;i<n;i++){
int opt, l, r, c;
cin >> opt >> l >> r >> c;
function<void(int)> Push_Down = [&](int k){
for(int i=L[belong[k]];i<=R[belong[k]];i++){
a[i] = (a[i] * mulazy[belong[k]] % MOD + addlazy[belong[k]]) % MOD;
}
mulazy[belong[k]] = 1;
addlazy[belong[k]] = 0;
};
if(opt == 0){
function<void(int, int, int)> add = [&](int l, int r, int c){
Push_Down(l);
for(int i=l;i<=min(R[belong[l]], r);i++){
a[i] = (a[i] + c) % MOD;
}
if(belong[l] == belong[r]) return;
Push_Down(r);
for(int i=belong[l]+1;i<=belong[r]-1;i++){
addlazy[i] = (addlazy[i] + c) % MOD;
}
for(int i=L[belong[r]];i<=r;i++){
a[i] = (a[i] + c) % MOD;
}
};
add(l, r, c);
}else if(opt == 1){
function<void(int, int, int)> mul = [&](int l, int r, int c){
Push_Down(l);
for(int i=l;i<=min(R[belong[l]], r);i++){
a[i] = (a[i] * c) % MOD;
}
if(belong[l] == belong[r]) return;
Push_Down(r);
for(int i=belong[l]+1;i<=belong[r]-1;i++){
addlazy[i] = (addlazy[i] * c) % MOD;
mulazy[i] = (mulazy[i] * c) % MOD;
}
for(int i=L[belong[r]];i<=r;i++){
a[i] = (a[i] * c) % MOD;
}
};
mul(l, r, c);
}else{
function<int(int)> query = [&](int r){
return (a[r] * mulazy[belong[r]] % MOD + addlazy[belong[r]]) % MOD;
};
cout << query(r) << '\n';
}
}
return 0;
}
8. 区间先查询数的个数,再全改为一个数
- 显然如果全改为一个数,我们可以给一整块做一个标记,这样查询到这个块的时候我们就可以使用这个信息,直接查询到结果,思路也是比较简单
#include <bits/stdc++.h>
using namespace std;
int main(){
ios::sync_with_stdio(false);
cin.tie(0);
int n;
cin >> n;
vector<int> a(n + 1), belong(n + 1);
int block = sqrt(n);
int tot = n / block;
if(n % block) tot += 1;
for(int i=1;i<=n;i++){
cin >> a[i];
belong[i] = (i - 1) / block + 1;
}
vector<int> L(tot + 1), R(tot + 1);
vector<int> vis(tot + 1, -1);
for(int i=1;i<=tot;i++){
L[i] = (i - 1) * block + 1;
R[i] = min(n, i * block);
}
for(int i=0;i<n;i++){
int l, r ,c;
cin >> l >> r >> c;
function<int(int, int, int)> query = [&](int l, int r, int c){
int ans = 0;
if(vis[belong[l]] != -1){
for(int i=L[belong[l]];i<=R[belong[l]];i++){
a[i] = vis[belong[l]];
}
}
for(int i=l;i<=min(r, R[belong[l]]);i++){
if(a[i] == c) ans += 1;
a[i] = c;
}
vis[belong[l]] = -1;
if(belong[l] == belong[r]) return ans;
if(vis[belong[r]] != -1){
for(int i=L[belong[r]];i<=R[belong[r]];i++){
a[i] = vis[belong[r]];
}
}
for(int i=L[belong[r]];i<=r;i++){
if(a[i] == c) ans += 1;
a[i] = c;
}
vis[belong[r]] = -1;
for(int i=belong[l]+1;i<=belong[r]-1;i++){
if(vis[i] == -1){
for(int j=L[i];j<=R[i];j++){
if(a[j] == c) ans += 1;
}
}
if(vis[i] == c){
ans += R[i] - L[i] + 1;
}
vis[i] = c;
}
return ans;
};
cout << query(l, r, c) << '\n';
}
return 0;
}
9. 求任意两点之间的区间众数
- 假设一共有 t o t tot tot个块,我们使用 d p [ i ] [ j ] dp[i][j] dp[i][j]表示从块 i i i到 j j j的区间最小众数,这个可以预处理出来,做法就是块之间的暴力,使用一些离散化的技巧就可以实现,具体看代码,这个时间复杂度是 O ( n n ) O(n\sqrt n) O(nn)
- 这样我们来考虑怎么高效求 [ l , r ] [l,r] [l,r]的区间众数,首先预处理出来每个数所在的块和每个块的左端点右端点这些信息,然后开一个桶,每个桶是一个数,桶的标号需要离散化处理,把相同的数的标号放在桶里,那么得到的这个桶内部标号是增序排列的,这样我们就可以使用二分来求出一个区间内有多少个某个数了,接下来我们使用块内暴力,块间记录的方法就可以解出这个题
- 这个题卡时间,常规的块大小为 n \sqrt n n会 T T T一个点,对于块大小的选择可以多试试,选择 n 2 \frac{\sqrt n}{2} 2n作为块的大小可以通过,或者也可以使用一些常数,比如100,也能通过这个题
- 最后注意一下我们要求的是区间众数的最小值
#include <bits/stdc++.h>
using namespace std;
int main(){
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
int n;
cin >> n;
int block = sqrt(n) / 2;
int tot = n / block;
if(n % block) tot += 1;
vector<int> a(n + 1);
vector<int> belong(n + 1);
unordered_map<int, int> mp;
int tt = 0;
vector<vector<int> > vs(n + 1);
vector<int> val(1);
vector<int> L(tot + 1);
vector<int> R(tot + 1);
for(int i=1;i<=n;i++){
cin >> a[i];
if(!mp.count(a[i])){
mp[a[i]] = ++tt;
val.push_back(a[i]);
}
a[i] = mp[a[i]];
belong[i] = (i - 1) / block + 1;
vs[a[i]].push_back(i);
}
vector<vector<int> > dp(tot + 1, vector<int> (tot + 1));
function<int(int, int, int)> Get = [&](int l, int r, int x){
return upper_bound(vs[x].begin(), vs[x].end(), r) - lower_bound(vs[x].begin(), vs[x].end(), l);
};
for(int i=1;i<=tot;i++){
L[i] = (i - 1) * block + 1;
R[i] = min(n, i * block);
function<void(int)> init = [&](int x){
vector<int> cnt(tt + 1);
int mx = INT_MIN;
int ans = 0;
for(int j=(x - 1) * block + 1;j<=n;j++){
cnt[a[j]] += 1;
if(cnt[a[j]] > mx || (cnt[a[j]] == mx && val[a[j]] < val[ans])){
mx = cnt[a[j]];
ans = a[j];
}
dp[x][belong[j]] = ans;
}
};
init(i);
}
for(int k=0;k<n;k++){
int l, r;
cin >> l >> r;
function<int(int, int)> query = [&](int l, int r){
if(belong[l] == belong[r]){
int mx = 0;
int ans = INT_MAX;
for(int i=l;i<=r;i++){
int tmp = Get(l, r, a[i]);
if(tmp > mx || (tmp == mx && val[ans] > val[a[i]])){
mx = tmp;
ans = a[i];
}
}
return val[ans];
}else{
int ans = dp[belong[l] + 1][belong[r] - 1];
int mx = Get(l, r, ans);
for(int i=l;i<=R[belong[l]];i++){
int tmp = Get(l, r, a[i]);
if(tmp > mx || (tmp == mx && val[ans] > val[a[i]])){
mx = tmp;
ans = a[i];
}
}
for(int i=L[belong[r]];i<=r;i++){
int tmp = Get(l, r, a[i]);
if(tmp > mx || (tmp == mx && val[ans] > val[a[i]])){
mx = tmp;
ans = a[i];
}
}
return val[ans];
}
};
cout << query(l, r) << '\n';
}
return 0;
}