题解 P1198 【[JSOI2008]最大数】
题解 P1198 【[JSOI2008]最大数】
思路
显而易见,后入队的结点如果比前面的大,那么前面的一定对答案没有贡献(如果查询的区间包括前面的,则一定包括后面的)
可以考虑维护单调性
算法1
维护一个递增的单调栈,每次询问二分查找第一个在询问范围内的
复杂度分析
- 每个元素只会入栈一次
- 每次查询的复杂度是
O(log_2n) 的 - 总复杂度
O(n+qlog_2n)
期望得分100分
算法2
考虑对算法1进行优化
我们发现算法1的查询较慢,考虑是否能
发现可以用并查集维护,每次插入将和它相邻并比它小的合并
每个集合的集合代表设为最左边的,集合权值为最右边的值,这样下次维护时对于一个集合可以直接跳到它的左边,而查询末尾
复杂度分析
- 每个元素作为右端点只会被合并一次,所以一次插入的复杂度均摊为
O(\alpha(n)) - 查询操作只需查询它所对应的集合的权值,所以一次查询的复杂度为
O(\alpha(n)) - 总复杂度
O(\alpha(n)\cdot(n+q))
期望得分100分
参考代码
此处只提供算法2的代码(省略预处理和快读快输)
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]; // 将集合权值设为右边的值
}