分块九题
NO.1
题目:
给出一个长为\(n\)的数列,以及\(n\)个操作,操作涉及区间加法,单点查值。
代码:
#include <iostream>
#include <cstdio>
#include <cstring>
#include <cmath>
using namespace std;
inline int read(void) {
int x = 0, f = 1; char ch = getchar();
while (ch < '0' || ch > '9') { if (ch == '-') f = -1; ch = getchar(); }
while (ch >= '0' && ch <= '9') { x = x * 10 + ch - '0'; ch = getchar(); }
return f * x;
}
const int maxn = 50005;
int n, L[maxn], R[maxn], pos[maxn], t;
long long a[maxn], add[maxn];
inline void update(int l, int r, int c) {
int p = pos[l], q = pos[r];
if (p == q) {
for (int i = l; i <= r; ++ i) a[i] += c;
return;
}
for (int i = l; i <= R[p]; ++ i) a[i] += c;
for (int i = L[q]; i <= r; ++ i) a[i] += c;
for (int i = p + 1; i <= q - 1; ++ i) add[i] += c;
}
int main() {
n = read();
t = sqrt(n);
for (int i = 1; i <= n; ++ i) {
a[i] = read();
}
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;
}
}
int Q = n;
while (Q --) {
int opt = read(), l = read(), r = read(), c = read();
if (opt == 0) {
update(l, r, c);
} else {
printf("%lld\n", a[r] + add[pos[r]]);
}
}
return 0;
}
NO.2
题目:
给出一个长为\(n\)的数列,以及\(n\)个操作,操作涉及区间加法,询问区间内小于某个值\(x\)的元素个数。
思路:
每次暴力修改块内元素时,暴力将块内元素重新排序。
代码:
#include <iostream>
#include <cstdio>
#include <cstring>
#include <cmath>
#include <algorithm>
#include <vector>
using namespace std;
inline int read(void) {
int x = 0, f = 1; char ch = getchar();
while (ch < '0' || ch > '9') { if (ch == '-') f = -1; ch = getchar(); }
while (ch >= '0' && ch <= '9') { x = x * 10 + ch - '0'; ch = getchar(); }
return f * x;
}
const int maxn = 50005;
int n, t, L[maxn], R[maxn], pos[maxn];
long long a[maxn], add[maxn];
vector<long long>vec[maxn];
inline void bf_update(int l, int r, long long c) {
int p = pos[l];
for (int i = l; i <= r; ++ i) {
a[i] += c;
}
vec[p].clear();
for (int i = L[p]; i <= R[p]; ++ i) {
vec[p].push_back(a[i]);
}
sort(vec[p].begin(), vec[p].end());
}
inline void update(int l, int r, long long c) {
int p = pos[l], q = pos[r];
if (p == q) {
bf_update(l, r, c);
return;
}
bf_update(l, R[p], c);
bf_update(L[q], r, c);
for (int i = p + 1; i <= q - 1; ++ i) {
add[i] += c;
}
}
inline int bf_count(int l, int r, long long c) {
int res = 0, p = pos[l];
for (int i = l; i <= r; ++ i) {
res += ((a[i] + add[p]) < c);
}
return res;
}
inline int query(int l, int r, long long c) {
int p = pos[l], q = pos[r];
if (p == q) {
return bf_count(l, r, c);
}
int res = 0;
res += bf_count(l, R[p], c);
res += bf_count(L[q], r, c);
for (int i = p + 1; i <= q - 1; ++ i) {
res += lower_bound(vec[i].begin(), vec[i].end(), c - add[i]) - vec[i].begin();
}
return res;
}
int main() {
n = read();
for (int i = 1; i <= n; ++ i) a[i] = read();
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;
vec[i].push_back(a[j]);
}
sort(vec[i].begin(), vec[i].end());
}
int Q = n;
while (Q --) {
int opt = read(), l = read(), r = read();
long long c = read();
if (opt == 0) {
update(l, r, c);
}
else {
printf("%d\n", query(l, r, c * c));
}
}
return 0;
}
NO.3
题目:
给出一个长为\(n\)的数列,以及\(n\)个操作,操作涉及区间加法,询问区间内小于某个值\(x\)的前驱(比其小的最大元素)。
思路:
和上题一样。不过这种做法不开O2就过不去,我觉得正解应该是分块套平衡树。大家不要被吓到,实际上平衡树用set代替就可以了。
代码:
#include <iostream>
#include <cstdio>
#include <cstring>
#include <cmath>
#include <vector>
#include <algorithm>
using namespace std;
inline int read(void) {
int x = 0, f = 1; char ch = getchar();
while (ch < '0' || ch > '9') { if (ch == '-') f = -1; ch = getchar(); }
while (ch >= '0' && ch <= '9') { x = x * 10 + ch - '0'; ch = getchar(); }
return f * x;
}
const int maxn = 100005, maxm = 320;
const long long inf = 9223372036854775807;
int n, t, L[maxm], R[maxm], pos[maxn];
long long a[maxn], add[maxm];
vector<long long>S[maxm];
inline void bf_update(int l, int r, long long c) {
int p = pos[l];
for (int i = l; i <= r; ++ i) a[i] += c;
S[p].clear();
for (int i = L[p]; i <= R[p]; ++ i) S[p].push_back(a[i]);
sort(S[p].begin(), S[p].end());
}
inline void update(int l, int r, long long c) {
int p = pos[l], q = pos[r];
if (p == q) {
bf_update(l, r, c); return;
}
bf_update(l, R[p], c);
bf_update(L[q], r, c);
for (int i = p + 1; i <= q - 1; ++ i) add[i] += c;
}
inline long long bf_query(int l, int r, long long c) {
long long mx = -inf;
int p = pos[l];
for (int i = l; i <= r; ++ i) {
if (a[i] + add[p] < c) mx = max(mx, a[i] + add[p]);
}
return mx;
}
inline long long query(int l, int r, long long c) {
int p = pos[l], q = pos[r];
if (p == q) {
long long tmp = bf_query(l, r, c);
if (tmp == -inf) return -1;
else return tmp;
}
long long mx = -inf;
mx = max(mx, bf_query(l, R[p], c));
mx = max(mx, bf_query(L[q], r, c));
for (int i = p + 1; i <= q - 1; ++ i) {
int x = lower_bound(S[i].begin(), S[i].end(), c - add[i]) - S[i].begin();
if (x == 0) continue;
-- x;
mx = max(mx, S[i][x] + add[i]);
}
if (mx == -inf) return -1;
else return mx;
}
int main() {
n = read(); t = sqrt(n);
for (int i = 1; i <= n; ++ i) a[i] = read();
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;
S[i].push_back(a[j]);
}
sort(S[i].begin(), S[i].end());
}
while (n --) {
int opt = read(), l = read(), r = read();
long long c = read();
if (opt == 0) {
update(l, r, c);
} else {
printf("%lld\n", query(l, r, c));
}
}
return 0;
}
NO.4
题目:
给出一个长为\(n\)的数列,以及\(n\)个操作,操作涉及区间加法,区间求和。
思路:
分块基操。
代码:
#include <iostream>
#include <cstdio>
#include <cstring>
#include <cmath>
using namespace std;
inline int read(void) {
int x = 0, f = 1; char ch = getchar();
while (ch < '0' || ch > '9') { if (ch == '-') f = -1; ch = getchar(); }
while (ch >= '0' && ch <= '9') { x = x * 10 + ch - '0'; ch = getchar(); }
return f * x;
}
const int maxn = 50005, maxm = 230;
int n, t, L[maxm], R[maxm], pos[maxn];
long long a[maxn], add[maxm], sum[maxm];
inline void update(int l, int r, long long c) {
int p = pos[l], q = pos[r];
if (p == q) {
for (int i = l; i <= r; ++ i) a[i] += c;
sum[p] += c * (r - l + 1);
return;
}
for (int i = l; i <= R[p]; ++ i) a[i] += c, sum[p] += c;
for (int i = L[q]; i <= r; ++ i) a[i] += c, sum[q] += c;
for (int i = p + 1; i <= q - 1; ++ i) add[i] += c, sum[i] += (R[i] - L[i] + 1) * c;
}
inline long long query(int l, int r) {
int p = pos[l], q = pos[r];
long long res = 0;
if (p == q) {
for (int i = l; i <= r; ++ i) res += a[i] + add[p];
return res;
}
for (int i = l; i <= R[p]; ++ i) res += a[i] + add[p];
for (int i = L[q]; i <= r; ++ i) res += a[i] + add[q];
for (int i = p + 1; i <= q - 1; ++ i) res += sum[i];
return res;
}
int main() {
n = read(); t = sqrt(n);
for (int i = 1; i <= n; ++ i) a[i] = read();
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; sum[i] += a[j];
}
}
while (n --) {
int opt = read(), l = read(), r = read();
long long c = read();
if (opt == 0) {
update(l, r, c);
} else {
long long res = query(l, r) % (c + 1);
if (res < 0) res += (c + 1);
printf("%lld\n", res);
}
}
return 0;
}
NO.5
题目:
给出一个长为\(n\)的数列\(a_1\ldots a_n\),以及\(n\)个操作,操作涉及区间开方,区间求和。
思路:
从本题开始,问题难度稍微提升。
容易发现,一个在\(1\sim 2^{31}-1\)的数,最多开5次根就可以把它变成\(1\)。
得到以下关于修改操作的算法:
边角暴力。对于一个整块,如果这个整块\(p\)满足\(sum(p)= L_p-R_p+1\)(也就是这个块中的元素全部是\(1\)),直接跳过这个块。否则暴力遍历块内的元素,进行修改。
看起来非常的暴力,但只不过将\(O(n\sqrt n)\)的复杂度多乘了一个常数而已。
代码:
#include <iostream>
#include <cstdio>
#include <cstring>
#include <cmath>
using namespace std;
inline int read(void) {
int x = 0, f = 1; char ch = getchar();
while (ch < '0' || ch > '9') { if (ch == '-') f = -1; ch = getchar(); }
while (ch >= '0' && ch <= '9') { x = x * 10 + ch - '0'; ch = getchar(); }
return f * x;
}
const int maxn = 50005, maxm = 230;
long long a[maxn], sum[maxm];
int n, t, L[maxm], R[maxm], pos[maxn];
inline void update(int l, int r) {
int p = pos[l], q = pos[r];
if (p == q) {
for (int i = l; i <= r; ++ i) {
sum[p] -= a[i];
a[i] = sqrt(a[i]);
sum[p] += a[i];
}
return;
}
for (int i = l; i <= R[p]; ++ i) {
sum[p] -= a[i];
a[i] = sqrt(a[i]);
sum[p] += a[i];
}
for (int i = L[q]; i <= r; ++ i) {
sum[q] -= a[i];
a[i] = sqrt(a[i]);
sum[q] += a[i];
}
for (int i = p + 1; i <= q - 1; ++ i) {
if (sum[i] == (R[i] - L[i] + 1)) continue;
for (int j = L[i]; j <= R[i]; ++ j) {
sum[i] -= a[j];
a[j] = sqrt(a[j]);
sum[i] += a[j];
}
}
}
inline long long query(int l, int r) {
int p = pos[l], q = pos[r];
long long res = 0;
if (p == q) {
for (int i = l; i <= r; ++ i) res += a[i];
return res;
}
for (int i = l; i <= R[p]; ++ i) res += a[i];
for (int i = L[q]; i <= r; ++ i) res += a[i];
for (int i = p + 1; i <= q - 1; ++ i) res += sum[i];
return res;
}
int main() {
n = read();
for (int i = 1; i <= n; ++ i) a[i] = read();
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;
sum[i] += a[j];
}
}
while (n --) {
int opt = read(), l = read(), r = read(); read();
if (opt == 0) {
update(l, r);
}
else {
printf("%lld\n", query(l, r));
}
}
return 0;
}
NO.6
题目:
给出一个长为\(n\)的数列,以及\(n\)个操作,操作涉及单点插入,单点询问,数据随机生成。
思路:
考虑对每个块建一个链表。
由于本题数据随机生成,所以块的大小相对比较平衡。如果数据不是随机生成的,我们就需要在每次插入的时候判断一下,如果该块的大小有点太大了,就暴力重构每个块。
代码:
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <list>
#include <cmath>
using namespace std;
inline int read(void) {
int x = 0, f = 1; char ch = getchar();
while (ch < '0' || ch > '9') { if (ch == '-') f = -1; ch = getchar(); }
while (ch >= '0' && ch <= '9') { x = x * 10 + ch - '0'; ch = getchar(); }
return f * x;
}
const int maxn = 200005, maxm = 505;
int n, a[maxn], t, tot;
list<int> arr[maxm];
inline void change(void) {// 重构
tot = 0;
for (int i = 1; i <= t; ++ i) {
while (arr[i].size()) { a[++ tot] = arr[i].front(); arr[i].pop_front(); }
}
t = sqrt(tot);
if (t * t < tot) ++ t;
for (int i = 1; i <= t; ++ i) {
for (int j = (i - 1) * t + 1; j <= min(i * t, tot); ++ j) {
arr[i].push_back(a[j]);
}
}
}
inline void update(int p, int x) {
int cnt = 0, i;
for (i = 1; i <= t; ++ i) {
cnt += arr[i].size();
if (cnt >= p) break;
}
cnt -= arr[i].size();
p -= (cnt + 1);
list<int>:: iterator it = arr[i].begin();
while (p --) ++ it;
arr[i].insert(it, x);
if ((int)arr[i].size() > t * 20) change();// 判断是否失去平衡
}
inline int query(int p) {
int cnt = 0, i;
for (i = 1; i <= t; ++ i) {
cnt += arr[i].size();
if (cnt >= p) break;
}
cnt -= arr[i].size();
p -= (cnt + 1);
list<int>:: iterator it = arr[i].begin();
while (p --) ++ it;
return (*it);
}
int main() {
n = read();
for (int i = 1; i <= n; ++ i) a[i] = read();
t = sqrt(n);
if (t * t < n) ++ t;
for (int i = 1; i <= t; ++ i) {
for (int j = (i - 1) * t + 1; j <= min(i * t, n); ++ j) {
arr[i].push_back(a[j]);
}
}
while (n --) {
int opt = read(), l = read(), r = read(); read();
if (opt == 0) {
update(l, r);
}
else {
printf("%d\n", query(r));
}
}
return 0;
}
NO.7
题目:
一个长为\(n\)的数列,以及\(n\)个操作,操作涉及区间乘法,区间加法,单点询问。
思路:
注意到这道题和前面几道题的不同之处。前面几道题在边角暴力的时候,“修改操作”和“标记”是完全独立的。也就是说,我暴力修改一个块中某些元素的值,不会影响标记。但是这道题不一样。对于一个块内的某些元素被修改时,以前的标记就会变得不正确。
举个例子,比如说有一个元素的值为\(a_i=3\),它属于块\(p\)。而且\(add_p=2\),\(mult_p=3\)。那么它真实的值就应该是\(3\times3+2=11\)。
现在如果我单独对这一个元素进行修改,比如说把它加\(1\)。那么修改后正确的值应该是\(11+1=12\)。但是如果我不管标记,直接将\(a_i\)先变成\(4\)。那么修改过后的值就变成了\(4\times 3+2\neq 12\)。这显然是错误的。
所以我们需要在边角暴力的时候,先将块重构一遍,再进行边角修改。
代码:
#include <iostream>
#include <cstdio>
#include <cstring>
#include <cmath>
using namespace std;
inline int read(void) {
int x = 0, f = 1; char ch = getchar();
while (ch < '0' || ch > '9') { if (ch == '-') f = -1; ch = getchar(); }
while (ch >= '0' && ch <= '9') { x = x * 10 + ch - '0'; ch = getchar(); }
return f * x;
}
const int maxn = 100005, mod = 10007, maxm = 350;
int n, t;
int a[maxn], L[maxn], R[maxn], pos[maxn], add[maxn], mult[maxn];
inline void rebuild(int p) {
for (int i = L[p]; i <= R[p]; ++ i) {
(a[i] *= mult[p]) %= mod;
(a[i] += add[p]) %= mod;
}
mult[p] = 1; add[p] = 0;
}
inline void update1(int l, int r, int c) {
int p = pos[l], q = pos[r];
if (p == q) {
rebuild(p);
for (int i = l; i <= r; ++ i) (a[i] += c) %= mod;
return;
}
rebuild(p);
for (int i = l; i <= R[p]; ++ i) (a[i] += c) %= mod;
rebuild(q);
for (int i = L[q]; i <= r; ++ i) (a[i] += c) %= mod;
for (int i = p + 1; i <= q - 1; ++ i) (add[i] += c) %= mod;
}
inline void update2(int l, int r, int c) {
int p = pos[l], q = pos[r];
if (p == q) {
rebuild(p);
for (int i = l; i <= r; ++ i) (a[i] *= c) %= mod;
return;
}
rebuild(p);
for (int i = l; i <= R[p]; ++ i) (a[i] *= c) %= mod;
rebuild(q);
for (int i = L[q]; i <= r; ++ i) (a[i] *= c) %= mod;
for (int i = p + 1; i <= q - 1; ++ i) {
(add[i] *= c) %= mod;
(mult[i] *= c) %= mod;
}
}
inline int query(int r) {
int q = pos[r];
return (a[r] * mult[q] % mod + add[q]) % mod;
}
int main() {
n = read();
for (int i = 1; i <= n; ++ i) {
a[i] = read() % mod;
}
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) {
mult[i] = 1;
for (int j = L[i]; j <= R[i]; ++ j) {
pos[j] = i;
}
}
for (int i = 1; i <= n; ++ i) {
int opt = read(), l = read(), r = read(), c = read() % mod;
if (opt == 0) {
update1(l, r, c);
}
else if (opt == 1) {
update2(l, r, c);
}
else if (opt == 2) {
printf("%d\n", query(r));
}
}
return 0;
}
NO.8
题目:
给出一个长为\(n\)的数列,以及\(n\)个操作,操作涉及区间询问等于一个数\(c\)的元素个数,并将这个区间的所有元素改为\(c\)。
思路:
个人认为这道题如果用线段树更为优雅,但是这是分块专题,就不得不想一想分块怎么做。
首先能想到一个非常暴力的想法。对于任意一个块\(p\),标记一下它有没有被覆盖过。覆盖指的是块\(p\)中的所有元素是否被赋成了同一个值。
对于一个操作,首先边角暴力。边角的块将标记下放,然后再进行暴力修改。然后对于中间的整块,如果这个块被覆盖过,那就直接统计答案,否则,暴力统计答案。
看起来复杂度非常的不对。理由是如果一次操作中的所有块全部没有被覆盖过,那么这一次操作的复杂度就是\(O(n)\)的。
但是,我们来想一个问题。如果初始时每个元素都相同,那么要想达到每个块都没有被完整地覆盖过,首先需要\(O(\sqrt n)\)次操作。所以感性的理解一下,每次操作的均摊复杂度仍是\(O(\sqrt n)\)的。
听起来比较玄学。而且这种方法之所以能用只是由于这道题目的特殊性(一次询问必定紧跟一次修改)。所以,博主的建议是,如果考试的时候遇到类似的问题,乖乖打线段树吧。
代码:
// 开O2才能过
#include <iostream>
#include <cstdio>
#include <cstring>
#include <cmath>
inline int read(void) {
int x = 0, f = 1; char ch = getchar();
while (ch < '0' || ch > '9') { if (ch == '-') f = -1; ch = getchar(); }
while (ch >= '0' && ch <= '9') { x = x * 10 + ch - '0'; ch = getchar(); }
return f * x;
}
const int maxn = 1e5 + 5, maxm = 320;
int n, a[maxn], t;
int L[maxm], R[maxm], pos[maxn], g[maxm];
bool tag[maxm];
inline int bf_update(int l, int r, int c) {
int p = pos[l];
if (tag[p]) {
for (int i = L[p]; i <= R[p]; ++ i) a[i] = g[p];
for (int i = l; i <= r; ++ i) a[i] = c;
tag[p] = false;
if (g[p] == c) return r - l + 1;
else return 0;
}
int res = 0;
for (int i = l; i <= r; ++ i) {
res += (a[i] == c);
a[i] = c;
}
return res;
}
inline int update(int l, int r, int c) {
int p = pos[l], q = pos[r];
if (p == q) {
return bf_update(l, r, c);
}
int res = 0;
res += bf_update(l, R[p], c);
res += bf_update(L[q], r, c);
for (int i = p + 1; i <= q - 1; ++ i) {
if (tag[i]) {
if (g[i] == c) res += R[i] - L[i] + 1;
}
else {
for (int j = L[i]; j <= R[i]; ++ j) {
res += (a[j] == c);
}
}
tag[i] = true;
g[i] = c;
}
return res;
}
int main() {
n = read(); t = sqrt(n);
for (int i = 1; i <= n; ++ i) a[i] = read();
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;
}
}
while (n --) {
int l = read(), r = read(), c = read();
printf("%d\n", update(l, r, c));
}
return 0;
}
NO.9
题目:
给出一个长为\(n\)的数列,以及\(n\)个操作,操作涉及询问区间的最小众数。
思路:
此题离线,当然可以用莫队直接秒掉。
但是分块的做法可以在线回答这些询问。
考虑将整个序列分成\(T\)块,每块的大小为\(n/T\)。首先预处理出pair数组\(mode\)。\(mode[i,j].second\)表示从\(L_i\)到\(R_j\)的最小众数,\(mode[i,j].first\)表示从\(L_i\)到\(R_j\)最小众数出现的次数。预处理的复杂度\(O(nT)\)。
再预处理出vector数组\(position\)。\(position[x]\)表示数值为\(x\)出现的位置集合。
对于一个询问\([l,r]\)。将询问分成三个部分:\([l,L),[L,R],(R,r]\)。
对于部分\([L,R]\),直接调用\(mode\)数组进行查询。
对于部分\([l,L)\),我们扫描\([l,L)\)中的每一个元素\(x\),在\(position\)数组中二分找出\(x\)在\([l,r]\)中出现了多少次,更新答案。
对于部分\((R,r]\),与上一部分类似。
每次每次询问的复杂度\(O(\dfrac{n}{T}\log_2n)\)。
所以总的复杂度\(O(nT+\dfrac{n^2\log_2n}{T})\)。运用均值不等式,得出\(T\)的最优取值\(T=\sqrt{n\log_2n}\)。最终的复杂度\(O(n\sqrt{n\log_2n})\)。
代码:
#include <iostream>
#include <cstdio>
#include <cstring>
#include <cmath>
#include <algorithm>
#include <vector>
using namespace std;
inline int read(void) {
int x = 0, f = 1; char ch = getchar();
while (ch < '0' || ch > '9') { if (ch == '-') f = -1; ch = getchar(); }
while (ch >= '0' && ch <= '9') { x = x * 10 + ch - '0'; ch = getchar(); }
return f * x;
}
const int maxn = 100005, maxm = 1300;
int n, a[maxn], t, L[maxm], R[maxm], pos[maxn], b[maxn], tot, len;
int cnt[maxn];
vector<int>position[maxn];
pair<int, int>mode[maxm][maxm];
inline void lsh(void) {// 别忘了离散化
for (int i = 1; i <= n; ++ i) b[i] = a[i];
sort(b + 1, b + n + 1);
tot = unique(b + 1, b + n + 1) - b - 1;
for (int i = 1; i <= n; ++ i) {
a[i] = lower_bound(b + 1, b + tot + 1, a[i]) - b;
}
double tmp = n;
t = max(sqrt(tmp * log2(tmp)), 1.0);
len = n / t;
}
inline int bf_query(int l, int r, int L, int R, int &num) {
int mx = 0, res = -1;
for (int i = l; i <= r; ++ i) {
int x = a[i];
int tmp1 = lower_bound(position[x].begin(), position[x].end(), L) - position[x].begin();
int tmp2 = upper_bound(position[x].begin(), position[x].end(), R) - position[x].begin();
if (tmp2 - tmp1 == res) {
mx = min(mx, x);
} else if (tmp2 - tmp1 > res) {
res = tmp2 - tmp1;
mx = x;
}
}
num = res;
return mx;
}
inline int query(int l, int r) {
int p = pos[l], q = pos[r], num = 0;
if (p == q) {
return bf_query(l, r, l, r, num);
}
int mx = (p == q - 1 ? 0 : mode[p + 1][q - 1].second), res = (p == q - 1 ? 0 : mode[p + 1][q - 1].first);
int tmp = bf_query(l, R[p], l, r, num);
if (num == res) mx = min(mx, tmp);
else if (num > res) { res = num; mx = tmp; }
tmp = bf_query(L[q], r, l, r, num);
if (num == res) mx = min(mx, tmp);
else if (num > res) { res = num; mx = tmp; }
return mx;
}
int main() {
n = read();
for (int i = 1; i <= n; ++ i) a[i] = read();
lsh();
for (int i = 1; i <= t; ++ i) {
L[i] = (i - 1) * len + 1;
R[i] = i * len;
}
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;
}
}
for (int i = 1; i <= t; ++ i) {
int p = L[i];
memset(cnt, 0, sizeof cnt);
int now = p - 1, mx = 0;
for (int j = i; j <= t; ++ j) {
while (now < R[j]) {
++ now;
++ cnt[a[now]];
if (cnt[a[now]] == cnt[mx]) {
mx = min(mx, a[now]);
} else if (cnt[a[now]] > cnt[mx]) mx = a[now];
}
mode[i][j].first = cnt[mx];
mode[i][j].second = mx;
}
}
for (int i = 1; i <= tot; ++ i) position[i].push_back(-1);
for (int i = 1; i <= n; ++ i) {
position[a[i]].push_back(i);
}
for (int i = 1; i <= tot; ++ i) position[i].push_back(n + 1);
for (int i = 1; i <= n; ++ i) {
int l = read(), r = read();
printf("%d\n", b[query(l, r)]);
}
return 0;
}
至此,分块九题的讲解全部结束。完结撒花。