[BZOJ 3489] A simple rmq problem 【可持久化树套树】

时间:2021-12-08 12:46:22

题目链接:BZOJ - 3489

题目分析

“因为是OJ上的题,就简单点好了。”——出题人

真的..好..简单...

首先,我们求出每个数的前一个与它相同的数的位置,即 prev[i] ,如果前面没有相同的数,prev[i] = 0。

再求出每个数的后一个与它相同的数的位置,即 next[i], 如果后面没有相同的数,next[i] = n + 1。

这样,对于 l > prev[i], r < next[i] 的区间,i 这个数在区间中至多出现一次。

那么我们要求的就是:符合 prev[i] < l, next[i] > r, l <= i <= r 的最大的 value[i] 。

这样我们可以用可持久化树套树来做。

将所有的数按照 prev[i] 从小到大排序,然后按照这个顺序建立外层的可持久化线段树,每棵可持久化线段树的节点代表的是 next[i] 的区间,每个节点都是一颗可持久化线段树,内层可持久化线段树的节点代表的是 i 的区间,即数在原数列中的位置。

每次 Insert 一个 i ,是在前一棵可持久化线段树的基础上根据这个 i 的 next[i] 增加一条链,然后链上的 logn 个节点都要更新一下它们的内层可持久化线段树,每棵内层线段树也是在之前的内层可持久化线段树的基础上新建一条链,这样总的空间复杂度是 O(n log^2n) 的。

询问 (l, r) 的时候,二分找到一个最大的 p ,满足 prev[p] < l,然后在 p 的外层可持久化线段树中查询 [r + 1, n + 1],即 next > r ,在这个区间的内层可持久化线段树中查找在 [l, r] 区间中的最大值。

时间复杂度也是 O(n log^2n) 的。

代码

#include <iostream>
#include <cstdlib>
#include <cstring>
#include <cstdio>
#include <cmath>
#include <algorithm> using namespace std; inline void Read(int &Num)
{
char c = getchar();
bool Neg = false;
while (c < '0' || c > '9')
{
if (c == '-') Neg = true;
c = getchar();
}
Num = c - '0'; c = getchar();
while (c >= '0' && c <= '9')
{
Num = Num * 10 + c - '0';
c = getchar();
}
if (Neg) Num = -Num;
} inline int gmin(int a, int b) {return a < b ? a : b;}
inline int gmax(int a, int b) {return a > b ? a : b;} const int MaxN = 100000 + 5, MaxNodeI = 2000000 + 5, MaxNodeII = 40000000 + 5; int n, m, Ans, IndexI, IndexII;
int Last[MaxN], Root_I[MaxN], Son_I[MaxNodeI][2], Root_II[MaxNodeI], Son_II[MaxNodeII][2], T[MaxNodeII]; struct ES
{
int Pos, Num, Prev, Next; bool operator < (const ES &b) const
{
return Prev < b.Prev;
}
bool operator < (const int &b) const
{
return Prev < b;
}
} E[MaxN]; void Insert_II(int &x, int Last, int s, int t, int Pos, int Num)
{
if (x == 0) x = ++IndexII;
T[x] = gmax(T[Last], Num);
if (s == t) return;
int m = (s + t) >> 1;
if (Pos <= m)
{
Son_II[x][1] = Son_II[Last][1];
Insert_II(Son_II[x][0], Son_II[Last][0], s, m, Pos, Num);
}
else
{
Son_II[x][0] = Son_II[Last][0];
Insert_II(Son_II[x][1], Son_II[Last][1], m + 1, t, Pos, Num);
}
} void Insert_I(int &x, int Last, int s, int t, int Nxt, int Pos, int Num)
{
if (x == 0) x = ++IndexI;
Insert_II(Root_II[x], Root_II[Last], 0, n + 1, Pos, Num);
if (s == t) return;
int m = (s + t) >> 1;
if (Nxt <= m)
{
Son_I[x][1] = Son_I[Last][1];
Insert_I(Son_I[x][0], Son_I[Last][0], s, m, Nxt, Pos, Num);
}
else
{
Son_I[x][0] = Son_I[Last][0];
Insert_I(Son_I[x][1], Son_I[Last][1], m + 1, t, Nxt, Pos, Num);
}
} int Get_II(int x, int s, int t, int l, int r)
{
if (x == 0) return 0;
if (l <= s && r >= t) return T[x];
int ret = 0, m = (s + t) >> 1;
if (l <= m) ret = gmax(ret, Get_II(Son_II[x][0], s, m, l, r));
if (r >= m + 1) ret = gmax(ret, Get_II(Son_II[x][1], m + 1, t, l, r));
return ret;
} int Get_I(int x, int s, int t, int l_I, int r_I, int l_II, int r_II)
{
if (x == 0) return 0;
if (l_I <= s && r_I >= t) return Get_II(Root_II[x], 0, n + 1, l_II, r_II);
int ret = 0, m = (s + t) >> 1;
if (l_I <= m) ret = gmax(ret, Get_I(Son_I[x][0], s, m, l_I, r_I, l_II, r_II));
if (r_I >= m + 1) ret = gmax(ret, Get_I(Son_I[x][1], m + 1, t, l_I, r_I, l_II, r_II));
return ret;
} int main()
{
scanf("%d%d", &n, &m);
for (int i = 1; i <= n; ++i)
{
Read(E[i].Num);
E[i].Pos = i;
E[i].Prev = Last[E[i].Num];
E[E[i].Prev].Next = i;
E[i].Next = n + 1;
Last[E[i].Num] = i;
}
sort(E + 1, E + n + 1);
for (int i = 1; i <= n; ++i)
Insert_I(Root_I[i], Root_I[i - 1], 0, n + 1, E[i].Next, E[i].Pos, E[i].Num);
Ans = 0;
int x, y, l, r, p;
for (int i = 1; i <= m; ++i)
{
Read(x); Read(y);
l = gmin((x + Ans) % n + 1, (y + Ans) % n + 1);
r = gmax((x + Ans) % n + 1, (y + Ans) % n + 1);
p = lower_bound(E + 1, E + n + 1, l) - E - 1;
Ans = Get_I(Root_I[p], 0, n + 1, r + 1, n + 1, l, r);
printf("%d\n", Ans);
}
return 0;
}