前言
对于分块这个算法,感觉非常的厉害。黄学长放在loj里的题目质量都很高,感谢黄学长给我们带来了这么多好的题目QwQ。
分块作为一种区间甚至是树或者更高级的数据结构都可以轻松解决的搞笑算法,其实质的内涵就是一个优美的暴力。
卖一个萌:\(loj\)的代码格式化的风格好好看(♥∀♥),如果有人有源码方便在评论区共享一下吗?(并不是强迫的)我下面放的所有代码都是
前言骚话到此为止。
代码解释
st(x)
表示\(x\)块的开始,\(block\times x-block+1\)en(x)
表示\(x\)块的结束,\(min(block\times x,n)\),其中\(n\)为整个数列的长度。
数列分块1
题目描述
给出一个长为\(n\)的数列,以及\(n\)个操作,操作涉及区间加法,单点查值。
分析
非常经典的问题,也就是区间加法和单点查找,借此机会在重新回顾一下分块的各个流程。
作为一种数据结构题,最最经典的题目有\(4\)个,分别是单点修改,单点查找,区间修改和区间求和,我们在学习线段树和树状数组的时候做的入门题目就是这个4道题目,很多时候也就是这些模板的变形。
分块,我们首先将一个数组分成以\(block\)长度为一块一块的数组,\(block\)的大小是\(sqrt(n)\),这是我们习惯的也是最优的。
以修改为例,如果要修改\([l,r]\)区间内的数,那么如果\(l\)和\(r\)处在相同的块中,或者是相邻的块中,我们就直接暴力修改,如果中间隔了几个块,那么就直接像线段树一样,在每一个数组上添加懒标记,代表这一块全都\(+\)上了某一个数。
那么单点查找也非常的简单,就是原来的数\(+\)所在块的懒标记。
ac代码
#include
#define ms(a, b) memset(a, b, sizeof(a))
#define LL long long
using namespace std;
inline int gi() {
int w = 0, x = 0;
char ch = 0;
while (!isdigit(ch)) w |= ch == '-', ch = getchar();
while (isdigit(ch)) x = (x << 1) + (x << 3) + (ch ^ 48), ch = getchar();
return w ? -x : x;
}
#define N 50005
int a[N], add[N], pos[N];
int n, block;
void update(int l, int r, int c) {
int ll = pos[l], rr = pos[r];
if (rr - ll <= 1)
for (int i = l; i <= r; i++) a[i] += c;
else {
for (int i = l; i <= ll * block; i++) a[i] += c;
for (int i = ll + 1; i < rr; i++) add[i] += c;
for (int i = (rr - 1) * block + 1; i <= r; i++) a[i] += c;
}
}
int main() {
ms(add, 0);
n = gi();
for (int i = 1; i <= n; i++) a[i] = gi();
block = (int)(sqrt(n) + 0.5);
for (int i = 1; i <= n; i++) pos[i] = (i - 1) / block + 1;
while (n--) {
int opt = gi(), l = gi(), r = gi(), c = gi();
if (opt == 0)
update(l, r, c);
else
printf("%d\n", add[pos[r]] + a[r]);
}
return 0;
}
数列分块2
题目描述
给出一个长为n的数列,以及n个操作,操作涉及区间加法,询问区间内小于某个值x的元素个数。
分析
这一道题目才能体现出分块算法的优美之处。我们都知道能用线段树维护的题目一般都有一个规律,状态满足可加性,也就是说两个答案是可以整合在一起的。(虽然方式比较多)
但是这道题目就不一样了,元素个数并不满足可加性,所以我们就用分块来做。
如果是两段的数,那么就直接暴力统计,如果是块内的,那么就用二分搜索查找直接查找最大的小于\(x\)的位置就可以了。
ac代码
#include
#define ms(a, b) memset(a, b, sizeof(a))
#define pb push_back
#define LL long long
using namespace std;
inline int gi() {
int w = 0, x = 0;
char ch = 0;
while (!isdigit(ch)) w |= ch == '-', ch = getchar();
while (isdigit(ch)) x = (x << 1) + (x << 3) + (ch ^ 48), ch = getchar();
return w ? -x : x;
}
int block, n;
#define N 50005
#define st(x) ((x - 1) * block + 1)
#define en(x) (x * block)
vector v[255];
int add[N], a[N], pos[N];
void reset(int x) {
v[x].clear();
for (int i = st(x); i <= min(en(x), n); i++) v[x].pb(a[i]);
sort(v[x].begin(), v[x].end());
}
void update(int l, int r, int c) {
int bl = pos[l], br = pos[r];
if (br - bl <= 1)
for (int i = l; i <= r; i++) a[i] += c;
else {
for (int i = l; i <= en(bl); i++) a[i] += c;
for (int i = st(br); i <= r; i++) a[i] += c;
for (int i = bl + 1; i <= br - 1; i++) add[i] += c;
}
reset(bl);
reset(br);
}
int query(int l, int r, int c) {
int bl = pos[l], br = pos[r], res = 0;
if (bl == br) {
for (int i = l; i <= r; i++)
if (add[bl] + a[i] < c)
res++;
} else if (bl + 1 == br) {
for (int i = l; i <= en(bl); i++)
if (add[bl] + a[i] < c)
res++;
for (int i = st(br); i <= r; i++)
if (add[br] + a[i] < c)
res++;
} else {
for (int i = l; i <= en(bl); i++)
if (add[bl] + a[i] < c)
res++;
for (int i = st(br); i <= r; i++)
if (add[br] + a[i] < c)
res++;
for (int i = bl + 1; i <= br - 1; i++)
res += lower_bound(v[i].begin(), v[i].end(), c - add[i]) - v[i].begin();
}
return res;
}
int main() {
n = gi();
block = (int)(sqrt(n) + 0.5);
for (int i = 1; i <= n; i++) a[i] = gi();
for (int i = 1; i <= n; i++) pos[i] = (i - 1) / block + 1, v[pos[i]].pb(a[i]);
for (int i = 1; i <= pos[n]; i++) sort(v[i].begin(), v[i].end());
for (int i = 1; i <= n; i++) {
int opt = gi(), l = gi(), r = gi(), c = gi();
if (opt == 0)
update(l, r, c);
else
printf("%d\n", query(l, r, c * c));
}
return 0;
}
数列分块3
题目描述
给出一个长为n的数列,以及n个操作,操作涉及区间加法,询问区间内小于某个值x的前驱(比其小的最大元素)。
分析
这道题目其实和2是差不多的,只需要修改一下二分的操作就可以了。但是数据比较大,第一次我想到的是分块套离散化,这样应该可以过掉,但是可以更加简单,用分块套一个\(set\)或者是其他的结构,这样方便其他的操作。
ac代码
#include
#define ms(a, b) memset(a, b, sizeof(a))
#define LL long long
using namespace std;
inline int gi() {
int w = 0, x = 0;
char ch = 0;
while (!isdigit(ch)) w |= ch == '-', ch = getchar();
while (isdigit(ch)) x = (x << 1) + (x << 3) + (ch ^ 48), ch = getchar();
return w ? -x : x;
}
int block, n;
#define N 100005
#define st(x) (block * x - block + 1)
#define en(x) (block * x)
int add[N], a[N], pos[N];
set sett[N];
void reset(int id, int x) {
sett[pos[id]].erase(a[id]);
a[id] += x;
sett[pos[id]].insert(a[id]);
}
void update(int l, int r, int v) {
int bl = pos[l], br = pos[r];
if (br == bl)
for (int i = l; i <= r; i++) reset(i, v);
else {
for (int i = l; i <= en(bl); i++) reset(i, v);
for (int i = st(br); i <= r; i++) reset(i, v);
if (br - bl > 1)
for (int i = bl + 1; i <= br - 1; i++) add[i] += v;
}
}
int query(int l, int r, int c) {
int bl = pos[l], br = pos[r], res = -1;
if (bl == br)
for (int i = l; i <= r; i++) {
if (add[bl] + a[i] < c)
res = max(res, add[bl] + a[i]);
}
else {
for (int i = l; i <= en(bl); i++)
if (add[bl] + a[i] < c)
res = max(res, add[bl] + a[i]);
for (int i = st(br); i <= r; i++)
if (add[br] + a[i] < c)
res = max(res, add[br] + a[i]);
if (br - bl > 1) {
for (int i = bl + 1; i <= br - 1; i++) {
set::iterator it = sett[i].lower_bound(c - add[i]);
if (it == sett[i].begin())
continue;
--it;
res = max(res, *it + add[i]);
}
}
}
return res;
}
int main() {
n = gi();
block = (int)(sqrt(n) + 0.5);
for (int i = 1; i <= n; i++) a[i] = gi();
for (int i = 1; i <= n; i++) pos[i] = (i - 1) / block + 1, sett[pos[i]].insert(a[i]);
for (int cas = 1; cas <= n; cas++) {
int opt = gi(), l = gi(), r = gi(), c = gi();
if (opt == 0)
update(l, r, c);
else
printf("%d\n", query(l, r, c));
}
return 0;
}
数列分块4
题目描述
给出一个长为n的数列,以及n个操作,操作涉及区间加法,区间求和。
分析
和\(1\)一样,是一道入门的题,区间加法我们已经了解了流程,那么参照区间修改的流程。
区间求和就只不完整的块上直接求和,完整的块上我们参照线段树区间懒标记下方的方式,是元素原来的和加上增量乘以块内元素的个数。
ac代码
#include
#define ms(a, b) memset(a, b, sizeof(a))
#define LL long long
using namespace std;
inline int gi() {
int w = 0, x = 0;
char ch = 0;
while (!isdigit(ch)) w |= ch == '-', ch = getchar();
while (isdigit(ch)) x = (x << 1) + (x << 3) + (ch ^ 48), ch = getchar();
return w ? -x : x;
}
inline LL gll() {
LL w = 0, x = 0;
char ch = 0;
while (!isdigit(ch)) w |= ch == '-', ch = getchar();
while (isdigit(ch)) x = (x << 1) + (x << 3) + (ch ^ 48), ch = getchar();
return w ? -x : x;
}
int block, n;
#define st(x) (block * x - block + 1)
#define en(x) (block * x)
#define N 50005
LL sum[N], a[N], add[N];
int pos[N];
void update(int l, int r, LL c) {
int bl = pos[l], br = pos[r];
if (bl == br) {
for (int i = l; i <= r; i++) a[i] += c, sum[bl] += c;
} else {
for (int i = l; i <= en(bl); i++) a[i] += c, sum[bl] += c;
for (int i = st(br); i <= r; i++) a[i] += c, sum[br] += c;
for (int i = bl + 1; i <= br - 1; i++) add[i] += c;
}
}
LL query(int l, int r, LL c) {
int bl = pos[l], br = pos[r];
LL res = 0;
if (bl == br)
for (int i = l; i <= r; i++) res = (res + a[i] + add[bl]) % c;
else {
for (int i = l; i <= en(bl); i++) res = (res + a[i] + add[bl]) % c;
for (int i = st(br); i <= r; i++) res = (res + a[i] + add[br]) % c;
for (int i = bl + 1; i <= br - 1; i++) res = (res + sum[i] + add[i] * block) % c;
}
return res;
}
int main() {
n = gi();
block = (int)(sqrt(n) + 0.5);
for (int i = 1; i <= n; i++) a[i] = gll();
for (int i = 1; i <= n; i++) pos[i] = (i - 1) / block + 1, sum[pos[i]] += a[i];
for (int cas = 1; cas <= n; cas++) {
int opt = gi(), l = gi(), r = gi();
LL c = gi();
if (opt == 0)
update(l, r, c);
else
printf("%lld\n", query(l, r, c + 1) % (c + 1));
}
return 0;
}
数列分块5
题目描述
给出一个长为n的数列,以及n个操作,操作涉及区间开方,区间求和。
分析
一开始我看到这个题目的时候还是非常懵逼的,区间开方还要求和??
无奈之下随手打了一个暴力,然后顺便输出了中间的数列,造了几个大数据就发现了后面的数列都变成了\(1\)。
我们都知道一个数开方了若干次之后就变成了\(1/0\),那么我们还是暴力。
考虑到非完整块,我们还是直接暴力,复杂度不超过\(2\sqrt{n}\)。
那么考虑到完整的块,我们还是暴力,但是如果这个序列中的所有数都变成了\(1\)或者是\(0\),那么就判定不需要再开放修改了。
ac代码
#include
#define ms(a, b) memset(a, b, sizeof(a))
#define LL long long
using namespace std;
inline int gi() {
int w = 0, x = 0;
char ch = 0;
while (!isdigit(ch)) w |= ch == '-', ch = getchar();
while (isdigit(ch)) x = (x << 1) + (x << 3) + (ch ^ 48), ch = getchar();
return w ? -x : x;
}
int block, n;
#define st(x) (x * block - block + 1)
#define en(x) (x * block)
#define N 50005
int fg[N], a[N], pos[N], sum[N];
void update(int l, int r) {
int bl = pos[l], br = pos[r];
if (bl == br)
for (int i = l; i <= r; i++) {
sum[bl] -= a[i];
a[i] = sqrt(a[i]);
sum[bl] += a[i];
}
else {
for (int i = l; i <= en(bl); i++) {
sum[bl] -= a[i];
a[i] = sqrt(a[i]);
sum[bl] += a[i];
}
for (int i = st(br); i <= r; i++) {
sum[br] -= a[i];
a[i] = sqrt(a[i]);
sum[br] += a[i];
}
for (int i = bl + 1; i <= br - 1; i++) {
if (fg[i])
continue;
sum[i] = 0;
fg[i] = 1;
for (int j = st(i); j <= en(i); j++) {
a[j] = sqrt(a[j]);
sum[i] += a[j];
if (a[j] > 1)
fg[i] = 0;
}
}
}
}
int query(int l, int r) {
int bl = pos[l], br = pos[r], res = 0;
if (bl == br)
for (int i = l; i <= r; i++) res += a[i];
else {
for (int i = l; i <= en(bl); i++) res += a[i];
for (int i = st(br); i <= r; i++) res += a[i];
for (int i = bl + 1; i <= br - 1; i++) res += sum[i];
}
return res;
}
int main() {
ms(fg, 0);
n = gi();
block = (int)(sqrt(n) + 0.5);
for (int i = 1; i <= n; i++) a[i] = gi(), pos[i] = (i - 1) / block + 1, sum[pos[i]] += a[i];
for (int cas = 1; cas <= n; cas++) {
int opt = gi(), l = gi(), r = gi(), c = gi();
if (opt == 0)
update(l, r);
else
printf("%d\n", query(l, r));
}
return 0;
}
数列分块6
题目描述
给出一个长为n的数列,以及n个操作,操作涉及单点插入,单点询问,数据随机生成。
分析
我们将所有分好块的数组都放到\(vector\)中(之后满足我们的插入条件),那么我们就考虑直接插入。
对于随机数据这样是可以做的,但是如果数据并不随机,也就是如果我们都把数插入到同一个块中,那么这样就会T掉。
我们考虑重新分块\(repart\)(重新分)
那么如果没到\(10\times block\)的时候,我们就重新分块。
复杂度应该是比\(n\times sqrt{n}\)大一点,还是可以过掉的。
ac代码
#include
#define ms(a, b) memset(a, b, sizeof(a))
#define pb push_back
#define LL long long
#define pii pair
#define Mp make_pair
#define fi first
#define se second
using namespace std;
inline int gi() {
int w = 0, x = 0;
char ch = 0;
while (!isdigit(ch)) w |= ch == '-', ch = getchar();
while (isdigit(ch)) x = (x << 1) + (x << 3) + (ch ^ 48), ch = getchar();
return w ? -x : x;
}
#define N 100005
vector v[705];
int a[N];
int n, sum, block;
pii query(int r) {
int x = 1;
while (r > (int)v[x].size()) r -= v[x].size(), x++;
return Mp(x, r - 1);
}
void repart() {
vector kkk;
kkk.clear();
for (int i = 1; i <= 705; i++) {
if (v[i].size() == 0)
break;
for (int j = 0; j < (int)v[i].size(); j++) kkk.pb(v[i][j]);
v[i].clear();
}
block = (int)(sqrt(n) + 0.5);
for (int i = 1; i <= sum; i++) v[(i - 1) / block + 1].pb(kkk[i - 1]);
}
void insert(int l, int c) {
pii pos = query(l);
v[pos.fi].insert(v[pos.fi].begin() + pos.se, c);
}
int main() {
n = gi();
block = (int)(sqrt(n) + 0.5);
for (int i = 1; i <= n; i++) v[(i - 1) / block + 1].pb(a[i] = gi());
int cnt = 0, limit = block;
sum = n;
for (int cas = 1; cas <= n; cas++) {
++cnt;
if (cnt == 10 * limit)
repart();
int opt = gi(), l = gi(), r = gi(), c = gi();
if (opt == 0)
insert(l, r), sum++;
else {
pii ans = query(r);
printf("%d\n", v[ans.fi][ans.se]);
}
}
return 0;
}
数列分块7
题目描述
给出一个长为n的数列,以及n个操作,操作涉及区间乘法,区间加法,单点询问。
分析
还是非常经典的入门题,考虑到乘法和加法的混合运算,那么需要考虑优先级。
我们将所有的懒标记都设为\(x\times mul+add\),其中\(x\)表示的是原来的数,\(mul\)是乘法懒标记,\(add\)是加法懒标记。
- 乘法:\((x \times mul+add)\times new\)也就是\(x\times mul \times new+add \times new\)
加法:\(x \times mul+add+new\)
根据上面的式子,更新懒标记就可以了ac代码
#include
#define ms(a, b) memset(a, b, sizeof(a))
#define LL long long
using namespace std;
inline int gi() {
int w = 0, x = 0;
char ch = 0;
while (!isdigit(ch)) w |= ch == '-', ch = getchar();
while (isdigit(ch)) x = (x << 1) + (x << 3) + (ch ^ 48), ch = getchar();
return w ? -x : x;
}
int block, n;
#define N 100005
#define mod 10007
#define st(x) (block * x - block + 1)
#define en(x) (block * x)
int mul[N], add[N], a[N], pos[N];
void pushdown(int x) {
if (add[x] == 0 && mul[x] == 1)
return;
for (int i = st(x); i <= en(x); i++) a[i] = ((a[i] * mul[x]) % mod + add[x]) % mod;
add[x] = 0;
mul[x] = 1;
}
void update_add(int l, int r, int c) {
int bl = pos[l], br = pos[r];
pushdown(bl);
pushdown(br);
if (bl == br)
for (int i = l; i <= r; i++) a[i] = (a[i] + c) % mod;
else {
for (int i = l; i <= en(bl); i++) a[i] = (a[i] + c) % mod;
for (int i = st(br); i <= r; i++) a[i] = (a[i] + c) % mod;
for (int i = bl + 1; i < br; i++) add[i] = (add[i] + c) % mod;
}
}
void update_mul(int l, int r, int c) {
int bl = pos[l], br = pos[r];
pushdown(bl), pushdown(br);
if (bl == br)
for (int i = l; i <= r; i++) a[i] = (a[i] * c) % mod;
else {
for (int i = l; i <= en(bl); i++) a[i] = (a[i] * c) % mod;
for (int i = st(br); i <= r; i++) a[i] = (a[i] * c) % mod;
for (int i = bl + 1; i < br; i++) mul[i] = (mul[i] * c) % mod, add[i] = (add[i] * c) % mod;
}
}
int main() {
n = gi();
block = sqrt(n);
for (int i = 1; i <= n; i++) a[i] = gi();
for (int i = 1; i <= n; i++) pos[i] = (i - 1) / block + 1;
for (int i = 1; i <= pos[n]; i++) mul[i] = 1, add[i] = 0;
for (int cas = 1; cas <= n; cas++) {
int opt = gi(), l = gi(), r = gi(), c = gi();
if (opt == 0)
update_add(l, r, c);
else if (opt == 1)
update_mul(l, r, c);
else
printf("%d\n", ((a[r] * mul[pos[r]]) + add[pos[r]]) % mod);
}
return 0;
}
数列分块8
题目描述
给出一个长为n的数列,以及n个操作,操作涉及区间询问等于一个数c的元素,并将这个区间的所有元素改为c。给出一个长为n的数列,以及n个操作,操作涉及区间询问等于一个数c的元素,并将这个区间的所有元素改为c。
分析
我们会发现如果数列再若干次修改之后,会变成若干段相同的数,那么我们参照数列分块5的做法。
维护每个分块是否只有一种权值,区间操作的时候,对于同权值的一个块就O(1)统计答案,否则暴力统计答案,并修改标记,不完整的块也暴力。
复杂度玄学能过。
ac代码
#include
#define ms(a, b) memset(a, b, sizeof(a))
#define pb push_back
#define LL long long
using namespace std;
inline int gi() {
int w = 0, x = 0;
char ch = 0;
while (!isdigit(ch)) w |= ch == '-', ch = getchar();
while (isdigit(ch)) x = (x << 1) + (x << 3) + (ch ^ 48), ch = getchar();
return w ? -x : x;
}
int n, block;
#define N 100005
#define st(x) (block * x - block + 1)
#define en(x) (block * x)
int tag[N], pos[N], a[N];
void pushdown(int x) {
if (tag[x] == -1)
return;
for (int i = st(x); i <= en(x); i++) a[i] = tag[x];
tag[x] = -1;
}
int solve(int l, int r, int c) {
int bl = pos[l], br = pos[r], res = 0;
pushdown(bl);
pushdown(br);
if (bl == br)
for (int i = l; i <= r; i++) (a[i] == c) ? res++ : a[i] = c;
else {
for (int i = l; i <= en(bl); i++) (a[i] == c) ? res++ : a[i] = c;
for (int i = st(br); i <= r; i++) (a[i] == c) ? res++ : a[i] = c;
for (int i = bl + 1; i < br; i++) {
if (tag[i] != -1)
(tag[i] != c) ? tag[i] = c : res += block;
else {
for (int j = st(i); j <= en(i); j++) (c == a[j]) ? res++ : a[j] = c;
tag[i] = c;
}
}
}
return res;
}
int main() {
n = gi();
block = sqrt(n);
for (int i = 1; i <= n; i++) a[i] = gi(), pos[i] = (i - 1) / block + 1;
for (int i = 1; i <= pos[n]; i++) tag[i] = -1;
for (int cas = 1; cas <= n; cas++) {
int l = gi(), r = gi(), c = gi();
printf("%d\n", solve(l, r, c));
}
return 0;
}
数列分块9
题目描述
给出一个长为n的数列,以及n个操作,操作涉及询问区间的最小众数。
分析
一道经典难题,这个在黄学长的博客里没有很好的讲解我来水一波
首先区间最小众数,还是考虑分块,我们预处理出\(a[i][j]\)表示\(i\)块到\(j\)块中的的众数。
那么考虑查询区间\([l,r]\),若在非完整块中,直接暴力求解。
不然我们假设\(l\)在\(bl\)块中\(r\)在\(br\)块中。
那么我们中间块的答案已经预处理出来了,那么我们的答案要不是在完整块中要不就是在非完整块中。
我们对于每一个不同的树,对其位置进行二分查找,和数列分块2差不多,就可以用查找出有多少个\(x\)了。
那么复杂度差不多是\((n+q)\sqrt{n}logn\)。
我们可以尝试着优化这个\(log\),我们可以定义\(f[i][x]\)数组表示在[0,i]中有多少个\(x\),和前缀和一样的思路,那么在\([l,r]\)区间内\(x\)的个数就是\(f[r][x]-f[l-1][x]\),这个预处理的时间应该需要\(O(n^{1.5})\)
同时对于每一个块\(block\),预处理出\(a[block][i][x]\)表示在这个块内的\(f\)数组。我们需要记录每一个块内的\(id\)值。
总的预处理时间是\(O(n^{1.5})\),我们就优化下来了。
ac代码(好久之前打的,码风有一点不一样)
#include
#include
#include
#include
#include
#include
#include
#include
using namespace std;
#define ll long long
const int maxn = 2e5 + 100;
vector q[maxn];
vector vis;
int belong[maxn], a[maxn];
int num[maxn];
int dp[2500][2500];
int n, block = 50;
void init(int x) {
memset(num, 0, sizeof(num));
int maxx = 0, ans = 0;
for (int i = (x - 1) * block + 1; i <= n; i++) {
num[a[i]]++;
if (num[a[i]] > maxx || (num[a[i]] == maxx && ans > a[i])) {
maxx = num[a[i]];
ans = a[i];
}
dp[x][belong[i]] = ans;
}
}
int cal(int st, int ed, int num) {
return upper_bound(q[num].begin(), q[num].end(), ed) - lower_bound(q[num].begin(), q[num].end(), st);
}
int ask(int st, int ed) {
int maxx = 0, ans = 0;
if (belong[st] == belong[ed]) {
for (int i = st; i <= ed; i++) {
int tmp = cal(st, ed, a[i]);
if (maxx < tmp || (maxx == tmp && ans > a[i])) {
ans = a[i];
maxx = tmp;
}
}
return ans;
}
for (int i = st; i <= min(ed, (belong[st] * block)); i++) {
int tmp = cal(st, ed, a[i]);
if (maxx < tmp || (maxx == tmp && ans > a[i])) {
ans = a[i];
maxx = tmp;
}
}
for (int i = (belong[ed] - 1) * block + 1; i <= ed; i++) {
int tmp = cal(st, ed, a[i]);
if (maxx < tmp || (maxx == tmp && ans > a[i])) {
ans = a[i];
maxx = tmp;
}
}
int tmp = cal(st, ed, dp[belong[st] + 1][belong[ed] - 1]);
if (maxx < tmp || (maxx == tmp && ans > dp[belong[st] + 1][belong[ed] - 1])) {
ans = dp[belong[st] + 1][belong[ed] - 1];
maxx = tmp;
}
return ans;
}
int main() {
scanf("%d", &n);
for (int i = 1; i <= n; i++) {
scanf("%d", &a[i]);
vis.push_back(a[i]);
belong[i] = (i - 1) / block + 1;
}
sort(vis.begin(), vis.end());
vis.erase(unique(vis.begin(), vis.end()), vis.end());
for (int i = 1; i <= n; i++) {
a[i] = lower_bound(vis.begin(), vis.end(), a[i]) - vis.begin();
}
for (int i = 1; i <= belong[n]; i++) {
init(i);
}
for (int i = 1; i <= n; i++) {
q[a[i]].push_back(i);
}
int Case = n, st, ed;
while (Case--) {
scanf("%d %d", &st, &ed);
printf("%d\n", vis[ask(st, ed)]);
}
return 0;
}