「洛谷P1198」[JSOI2008]最大数 题解

题目

思路

显而易见,后入队的结点如果比前面的大,那么前面的一定对答案没有贡献(如果查询的区间包括前面的,则一定包括后面的)

可以考虑维护单调性

算法1

维护一个递增的单调栈,每次询问二分查找第一个在询问范围内的

复杂度分析

  1. 每个元素只会入栈一次
  2. 每次查询的复杂度是\(O(\log_2n)\)
  3. 总复杂度\(O(n+q\log_2n)\)

期望得分100分

算法2

考虑对算法1进行优化

我们发现算法1的查询较慢,考虑是否能\(O(1)\)查询,考虑不将对答案无贡献的点删去,而是维护比他大的点中最新插入进序列的

发现可以用并查集维护,每次插入将和它相邻并比它小的合并

每个集合的集合代表设为最左边的,集合权值为最右边的值,这样下次维护时对于一个集合可以直接跳到它的左边,而查询末尾\(k\)个数的值就是从右往左第\(k\)个数所属集合代表的权值

复杂度分析

  1. 每个元素作为右端点只会被合并一次,所以一次插入的复杂度均摊为\(O(\alpha(n))\)
  2. 查询操作只需查询它所对应的集合的权值,所以一次查询的复杂度为\(O(\alpha(n))\)
  3. 总复杂度\(O(\alpha(n)\cdot(n+q))\)

期望得分100分

参考代码

此处只提供算法2的代码(省略预处理和快读快输)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
const int N = 200005;

int find(int);
void uni(int, int);

int fa[N], maxi[N]; // 集合代表和权值
int id; // 数列的大小

int main () {
int n, m;
read(n), read(m);
int lastans = 0;
for (int i = 1; i <= n; ++i) {
char ch = getchar();
if (ch != 'A' && ch != 'Q') {
ch = getchar();
}
ll a;
read(a);
if (ch == 'A') {
++id;
fa[id] = id;
maxi[id] = (a + lastans) % m;
int fi = id; // 目前集合的最左端,fi - 1就是下次考虑是否合并的集合的最右端
while (fi > 1 && maxi[fi] > maxi[find(fi - 1)]) {
uni(fi - 1, fi);
fi = find(id);
}
} else {
lastans = maxi[find(id - a + 1)];
write(lastans), EL;
}
}
return 0;
}

int find(int x) {
return fa[x] == x ? x : fa[x] = find(fa[x]);
}
void uni(int x, int y) {
int fx = find(x), fy = find(y);
fa[fy] = fx; // 将集合代表设为左边的
maxi[fx] = maxi[fy]; // 将集合权值设为右边的值
}


「洛谷P1198」[JSOI2008]最大数 题解
https://blog.seniorious.cc/2019/luogu-1198/
作者
Seniorious
发布于
2019年8月10日
许可协议