##[$AcWing$ $1275$. 最大数](https://www.acwing.com/problem/content/1277/) ### 一、题目描述 给定一个正整数数列 $a_1,a_2,…,a_n$,每一个数都在 $0∼p−1$ 之间。 可以对这列数进行两种操作: 添加操作:向序列后添加一个数,序列长度变成 $n+1$; 询问操作:询问这个序列中最后 $L$ 个数中最大的数是多少。 程序运行的最开始,整数序列为空。 一共要对整数序列进行 $m$ 次操作。 写一个程序,读入操作的序列,并输出询问操作的答案。 **输入格式** 第一行有两个正整数 $m,p$,意义如题目描述; 接下来 $m$ 行,每一行表示一个操作。 如果该行的内容是 $Q$ $L$,则表示这个操作是询问序列中最后 $L$ 个数的最大数是多少; 如果是 $A$ $t$,则表示向序列后面加一个数,加入的数是 $(t+a)$ $mod$ $p$。其中,$t$ 是输入的参数,$a$ 是在这个添加操作之前最后一个询问操作的答案(如果之前没有询问操作,则 $a=0$)。 第一个操作一定是添加操作。对于询问操作,$L>0$ 且不超过当前序列的长度。 **输出格式** 对于每一个询问操作,输出一行。该行只有一个数,即序列中最后 $L$ 个数的最大数。 **数据范围** $1≤m≤2×10^5,1≤p≤2×10^9,0≤t #include typedef long long LL; using namespace std; const int N = 2e5 + 10; int tr[N]; int m, p; #define lowbit(x) (x & -x) void add(int n, int x) { for (int i = n; i <= m; i += lowbit(i)) tr[i] = max(tr[i], x); } int query(int n) { int ret = 0; for (int i = n; i; i -= lowbit(i)) ret = max(ret, tr[i]); return ret; } int main() { //加快读入 ios::sync_with_stdio(false), cin.tie(0); cin >> m >> p; int last = 0; int j = m; for (int i = 0; i < m; i++) { LL x; char c; cin >> c >> x; if (c == 'A') add(j--, (x + last) % p); else { last = query(j + x); printf("%d\n", last); } } return 0; } ``` ### 三、线段树求最大值模板代码 运行时间:$646$ $ms$ ```cpp {.line-numbers} #include using namespace std; typedef long long LL; const int N = 200010; int m; // m个操作 int p; // mod p // 线段树求最大值模板 #define ls (u << 1) #define rs (u << 1 | 1) #define mid ((l + r) >> 1) struct Node { int l, r, len; int v; } tr[N << 2]; void pushup(int u) { tr[u].v = max(tr[ls].v, tr[rs].v); } void build(int u, int l, int r) { tr[u].l = l, tr[u].r = r; if (l == r) return; build(ls, l, mid), build(rs, mid + 1, r); } int query(int u, int L, int R) { int l = tr[u].l, r = tr[u].r; if (l >= L && r <= R) return tr[u].v; if (l > R || r < L) return 0; return max(query(ls, L, R), query(rs, L, R)); } void modify(int u, int x, int v) { int l = tr[u].l, r = tr[u].r; if (l == r) { tr[u].v = v; return; } if (x <= mid) modify(u << 1, x, v); else modify(u << 1 | 1, x, v); pushup(u); } int n, last; int main() { // 加快读入 ios::sync_with_stdio(false), cin.tie(0); cin >> m >> p; // 单点修改,求区间最大值 // ① 初始化线段树,最多m次操作,最多m个数,区间范围[1, m] build(1, 1, m); int x; char op; while (m--) { cin >> op >> x; if (op == 'A') { // 对于节点++n进行修改,值=(最后一次的last查询值 + x )%p modify(1, ++n, ((LL)last + x) % p); } else { // u = 1:从根节点开始查询 // 查询序列中最后x个数的最大数: 查询[n - x + 1, n]内的最大值 last = query(1, n - x + 1, n); printf("%d\n", last); } } return 0; } ``` ### 四、$ST$表解法 大部分大佬用的是线段树(我第一次也用的线段树) 现在发现用$st$超级容易 每次插入只需要修改与最后一个点有关的值即可 典型的空间换时间; 线段树空间复杂度$O(4N)$,查询插入都是$O(logN)$,不过时间里面还有许多浪费的成分 $st$空间复杂度$O(NlogN)$,查询$O(1)$,插入$O(logN)$,比线段树快至少一半 由于$st$表还没有复习到,这里先不深入研究$st$表的解法,后面第三刷时再来仔细研究: ```cpp {.line-numbers} #include #include #include using namespace std; typedef long long ll; const int N=2e5+10,M=log2(N)+10; int f[N][M]; int query(int l,int r){ int k=log2(r-l+1); return max(f[l][k],f[r-(1<