• AcWing 1275. 最大数


    \(AcWing\) \(1275\). 最大数

    一、题目描述

    给定一个正整数数列 \(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 个数的最大数。

    二、线段树求最大值模板代码

    运行时间:\(646\) \(ms\)

    #include <bits/stdc++.h>
    
    using namespace std;
    typedef long long LL;
    const int N = 200010;
    int m; // m个操作
    int p; // mod p
    
    //线段树求最大值模板
    struct Node {
        int l, r, v; // 区间[l, r]中的最大值v
    } tr[N << 2];    // 开4倍空间
    
    void pushup(int u) {
        tr[u].v = max(tr[u << 1].v, tr[u << 1 | 1].v);
    }
    
    void build(int u, int l, int r) {
        tr[u] = {l, r};
        if (l == r) return;
        int mid = (l + r) >> 1;
        build(u << 1, l, mid), build(u << 1 | 1, mid + 1, r);
    }
    
    int query(int u, int l, int r) {
        if (tr[u].l >= l && tr[u].r <= r) return tr[u].v;
        int mid = (tr[u].l + tr[u].r) >> 1;
        int v = 0;
        if (l <= mid) v = query(u << 1, l, r);
        if (r > mid) v = max(v, query(u << 1 | 1, l, r));
        return v;
    }
    
    void modify(int u, int x, int v) {
        if (tr[u].l == tr[u].r) {
            tr[u].v = v;
            return;
        }
        int mid = (tr[u].l + tr[u].r) >> 1;
        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;
        build(1, 1, m); //初始化线段树, 区间范围[1, m]。
    
        int x;
        char op;
        while (m--) {
            cin >> op >> x;
            if (op == 'A') {
                modify(1, n + 1, ((LL)last + x) % p); //对于节点n+1进行修改,值=(最后一次的last查询值 + x )%p
                n++;
            } else {
                // u = 1:从根节点开始查询
                //查询序列中最后x个数的最大数: 查询[n - x + 1, n]内的最大值
                last = query(1, n - x + 1, n);
                printf("%d\n", last);
            }
        }
        return 0;
    }
    

    三、树状数组解法

    运行时间: \(246\) \(ms\)

    因为每次询问的区间是最后几个数字,而树状数组可以快速求前缀,所以不妨倒着存储数据。

    为了减少时间复杂度,要提前预留\(m\)个坑位,第一个数存放到\(m\),第二个数存放到\(m-1\),以此类推。

    #include <iostream>
    #include <cstdio>
    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;
    }
    

    四、\(ST\)表解法

    大部分大佬用的是线段树(我第一次也用的线段树)
    现在发现用\(st\)超级容易
    每次插入只需要修改与最后一个点有关的值即可
    典型的空间换时间;
    线段树空间复杂度\(O(4N)\),查询插入都是\(O(logN)\),不过时间里面还有许多浪费的成分
    \(st\)空间复杂度\(O(NlogN)\),查询\(O(1)\),插入\(O(logN)\),比线段树快至少一半

    由于\(st\)表还没有复习到,这里先不深入研究\(st\)表的解法,后面第三刷时再来仔细研究:
    \(2022-12-13\) 新冠非常猖獗的时候,记录一下生活~

    #include <iostream>
    #include <cstdio>
    #include <cmath>
    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<<k)+1][k]);
    }
    int n;
    int insert(int a){
        f[++n][0]=a;
        for(int i=1;1<<i<=n;i++)
            f[n-(1<<i)+1][i]=max(f[n-(1<<i)+1][i-1],f[n-(1<<i-1)+1][i-1]);
    }
    int q,p,a;
    char op[2];
    int x;
    int main(){
        scanf("%d%d",&q,&p);
        for(;q--;){
            scanf("%s%d",op,&x);
            if(op[0]=='A')
                insert((ll(x)+a)%p);
            else{
                printf("%d\n",a=query(n-x+1,n));
            }
        }
        return 0;
    }
    
    
  • 相关阅读:
    (二)正反向代理
    (一)服务器架构详解
    斐波那契数列
    旋转数组的最小值
    23 入栈 出栈
    重建二叉树
    22 大端序和小端序
    反转单链表
    替换空格
    二维数组中的查找
  • 原文地址:https://www.cnblogs.com/littlehb/p/16152232.html
Copyright © 2020-2023  润新知