题解:AcWing 1275 最大数
【题目来源】
AcWing:1275 最大数 - AcWing题库
【题目描述】
给定一个正整数数列\(a_1,a_2,...,a_n\),每一个数都在\(0\sim p-1\)之间。可以对这列数进行两种操作:
1.添加操作:向序列后添加一个数,序列长度变成 \(n+1\);
2.询问操作:询问这个序列中最后 \(L\) 个数中最大的数是多少。
程序运行的最开始,整数序列为空。一共要对整数序列进行\(m\)次操作。
写一个程序,读入操作的序列,并输出询问操作的答案。
【输入】
第一行有两个正整数\(m,p\),意义如题目描述;接下来\(m\)行,每一行表示一个操作。
如果该行的内容是\(Q\ L\),则表示这个操作是询问序列中最后 \(L\) 个数的最大数是多少;
如果是\(A\ t\),则表示向序列后面加一个数,加入的数是\((t+a)\ mod\ p\)。其中,\(t\)是输入的参数,\(a\)是在这个添加操作之前最后一个询问操作的答案(如果之前没有询问操作,则a=0)。
第一个操作一定是添加操作。对于询问操作,\(L>0\)且不超过当前序列的长度。
【输出】
对于每一个询问操作,输出一行。该行只有一个数,即序列中最后 \(L\) 个数的最大数。
【输入样例】
5 100
A 96
Q 1
A 97
Q 1
Q 2
【输出样例】
96
93
96
【核心思想】
-
问题分析:维护一个动态增长的序列,支持两种操作:在末尾添加一个数(值为 \((t + a) \bmod p\),\(a\) 为上次询问答案),以及查询序列最后 \(L\) 个数的最大值。序列长度上限为 \(m\),需要高效支持单点更新和区间最值查询。关键观察是:序列只增不减,可以用线段树维护,预先建树至最大长度,通过单点更新和区间查询实现 \(O(\log m)\) 操作。
-
算法选择:
- 线段树(Segment Tree):完全二叉树结构,每个节点维护区间最值,支持 \(O(\log n)\) 的单点更新和区间查询
- 离线建树:预先根据最大操作次数 \(m\) 建立空树,实际使用时逐步填充
-
关键步骤:
- 建树
build(u, l, r):- 若 \(l = r\),叶子节点对应单个位置
- 否则递归建立左右子树,\(mid = \lfloor \frac{l+r}{2} \rfloor\)
- 单点更新
update(u, l, r, d):- 若当前节点区间完全包含目标位置,直接赋值
tr[u].v = d - 否则递归更新左子树或右子树
- 回溯时
pushup:\(tr[u].v = \max(tr[u \ll 1].v, tr[u \ll 1 | 1].v)\)
- 若当前节点区间完全包含目标位置,直接赋值
- 区间查询
query(u, l, r):- 若当前节点区间完全包含于查询区间,返回
tr[u].v - 否则递归查询左右子树,取最大值
- 若当前节点区间完全包含于查询区间,返回
- 处理操作:
A t:\(n \leftarrow n + 1\),在位置 \(n\) 更新值为 \((last + t) \bmod p\)Q L:查询区间 \([n - L + 1, n]\) 的最大值,更新 \(last\) 为查询结果
- 建树
-
时间/空间复杂度:
- 时间复杂度:\(O(m \log m)\),每次更新和查询均为 \(O(\log m)\)
- 空间复杂度:\(O(m)\),线段树数组大小为 \(4m\)
-
线段树的核心思想:
- 分治区间维护:将序列划分为若干不相交的区间,每个节点维护一个区间的聚合信息(最值)。查询时通过 \(O(\log n)\) 个节点的信息合并得到结果
- 懒更新与即时更新:本题只需单点更新,无需懒标记(
pushdown)。pushup在子树修改后自底向上更新父节点,确保路径上所有节点信息正确 - 位运算优化子节点访问:\(u \ll 1\) 表示左子节点 \(2u\),\(u \ll 1 | 1\) 表示右子节点 \(2u+1\),避免乘法运算
- 预分配空间:根据最大长度 \(m\) 预先建树,避免动态扩容,实际使用时只更新已添加的位置
- 适用于动态序列的区间查询、单点/区间修改、RMQ 等场景,是树状数组和 ST 表的灵活替代方案
【算法标签】
线段树
【代码详解】
#include <bits/stdc++.h>
using namespace std;
#define int long long
const int N = 200005;
int m, p;
int w[N];
struct Node
{
int l, r;
int v; // 区间[l, r]中的最大值
}tr[N*4];
void pushup(int u)
{
tr[u].v = max(tr[u<<1].v, tr[u<<1|1].v); // 求最小值
}
// void pushdown(int u)
// {
// auto &root = tr[u], &l = tr[u<<1], &r = tr[u<<1|1];
// l.dt += root.dt, l.mn += root.dt;
// r.dt += root.dt, r.mn += root.dt;
// root.dt = 0;
// }
void build(int u, int l, int r)
{
if (l==r) tr[u] = {l, r};
else
{
tr[u] = {l, r};
int mid = l+r >> 1;
build (u<<1, l, mid), build(u<<1|1, mid+1, r);
pushup(u);
}
}
void update(int u, int l, int r, int d)
{
// cout << "u l r d " << u << " " << l << " " << r << " " << d << endl;
if (tr[u].l>=l && tr[u].r<=r)
{
tr[u].v = d;
}
else
{
// pushdown(u);
int mid = tr[u].l + tr[u].r >> 1;
if (l<=mid) update(u<<1, l, r, d);
if (r>mid) update(u<<1|1, l, r, d);
pushup(u);
}
}
int query(int u, int l, int r)
{
if (tr[u].l>=l && tr[u].r<=r)
{
return tr[u].v;
}
else
{
// pushdown(u);
int mid = tr[u].l + tr[u].r >> 1;
int res = 0;
if (l<=mid) res = query(u<<1, l, r);
// cout << "res1 " << res << endl;
if (r>mid) res = max(res, query(u<<1|1, l, r)); // 求最小值
// cout << "res2 " << res << endl;
return res;
}
}
signed main()
{
int n = 0, last = 0;
cin >> m >> p;
build(1, 1, m);
int x;
char op[2];
while (m--)
{
// scanf("%s%d", op, &x);
cin >> op >> x;
if (op[0]=='Q')
{
last = query(1, n-x+1, n);
printf("%d\n", last);
}
else
{
// cout << n+1 << " " << (last+x)%p << endl;
update(1, n+1, n+1, (last+x)%p);
n++;
}
}
return 0;
}
【运行结果】
5 100
A 96
Q 1
96
A 97
Q 1
93
Q 2
96
浙公网安备 33010602011771号