题解: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

【核心思想】

  1. 问题分析:维护一个动态增长的序列,支持两种操作:在末尾添加一个数(值为 \((t + a) \bmod p\)\(a\) 为上次询问答案),以及查询序列最后 \(L\) 个数的最大值。序列长度上限为 \(m\),需要高效支持单点更新和区间最值查询。关键观察是:序列只增不减,可以用线段树维护,预先建树至最大长度,通过单点更新和区间查询实现 \(O(\log m)\) 操作。

  2. 算法选择

    • 线段树(Segment Tree):完全二叉树结构,每个节点维护区间最值,支持 \(O(\log n)\) 的单点更新和区间查询
    • 离线建树:预先根据最大操作次数 \(m\) 建立空树,实际使用时逐步填充
  3. 关键步骤

    • 建树 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\) 为查询结果
  4. 时间/空间复杂度

    • 时间复杂度:\(O(m \log m)\),每次更新和查询均为 \(O(\log m)\)
    • 空间复杂度:\(O(m)\),线段树数组大小为 \(4m\)
  5. 线段树的核心思想

    • 分治区间维护:将序列划分为若干不相交的区间,每个节点维护一个区间的聚合信息(最值)。查询时通过 \(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
posted @ 2026-07-02 22:47  团爸讲算法  阅读(5)  评论(0)    收藏  举报