AcWing 3662. 最大上升子序列和

AcWing 3662. 最大上升子序列和

一、题目描述

给定一个长度为 n 的整数序列 a1,a2,,an

请你选出一个该序列的 严格上升子序列要求所选子序列的各元素之和尽可能大

请问这个 最大值 是多少?

输入格式
第一行包含整数 n

第二行包含 n 个整数 a1,a2,,an

输出格式
输出最大的上升子序列和。

数据范围
对于前三个测试点,1n4

对于全部测试点,1n105,1ai109

输入样例1

2
100 40

输出样例1

100

输入样例2

4
1 9 7 10

输出样例2

20

样例解释
对于样例 1,我们只选取 100

对于样例 2,我们选取 1,9,10

二、前置知识

1. 离散化

2. 树状数组 or 线段树 (用于维护前缀的信息)

3. 最长上升子序列 AcWing 895. 最长上升子序列

三、分析

首先这道题在不考虑优化的情况下是一道最长上升子序列板子题,不会的先去看一下 这道题 ,由于本题数据范围过大,我们需要考虑如何优化,这也是本题的 难点 所在。

我们先从DP的角度讨论一下这题的朴素写法

本题的状态转移方程见上图,显然,我们在执行状态方程的途中需要寻找某一个前缀的最大值,这使得我们每次执行方程都会遍历一遍前面的每一个数,这就导致了O(n2) 的时间复杂度,我们需要考虑一下如何将这一维优化掉。

由于我们每次寻找的f(k)max是一个前缀中的最大值,我们很容易可以想到树状数组或者线段树,来动态的维护前缀的最大值吗,这样我们每次查询就可以从O(n)优化到O(logn)

那么我们如何来维护这样一个前缀呢?

比较容易想到的是将f(i)维护到下标i,如下图所示

但是这样做在本题中是否行得通呢?

先说结论,本题不可以这样维护,由于题目明确规定,我们要找的子序列为单调上升子序列,所以必须满足一个条件ai>ak,如果我们维护上面这样一个关系,虽然可以让我们找到前缀中最大的一个f(k),但是我们却无法保证ai>ak

那我们如何维护呢?

我们考虑将f(i)维护到下标ai,如下图所示

如果我们采取这种维护策略,若a2>ai,则我们在查询ai的前缀时,就永远也无法找到f(2),这样就保证了ai>ak 这个条件始终是成立的。

但是,到这里还有最后一个问题,1ai109,如果我们直接维护,那么需要开一个大小为109的数组才可以做到,这显然是不可以接受的,同时我们发现1n105,这说明我们可以离散化一下,把每一个ai按照相对位置关系,一一映射到1n105 这个区间上,若文字难以理解,可以看下面的图。

离散化需要去重,由于本题的答案是取决于单调上升子序列并且子序列内部元素不能相等,所以去重不影响答案,我们需要的只是每个元素的相对位置不变,以及相对位置信息。

四、Code

#include <bits/stdc++.h>
using namespace std;
const int N = 100010;
typedef long long LL;      // 本题中 a 的上线是 10 ^ 9,故答案可能会超过 int 的上限
#define lowbit(x) (x & -x) // lowbit函数模板

int n, a[N];

int b[N], bl;  // 离散化数组
LL tr[N], res; // 树状数组,答案

void add(int x, LL c) {
    for (; x <= bl; x += lowbit(x)) tr[x] = max(tr[x], c);
}

// 查询x范围内,元素和最大值
LL query(int x) {
    LL res = 0;
    for (; x; x -= lowbit(x)) res = max(res, tr[x]);
    return res;
}

int main() {
    scanf("%d", &n);
    for (int i = 0; i < n; i++) {
        scanf("%d", a + i);
        b[i] = a[i];
    }
    // 离散化
    sort(b, b + n);
    bl = unique(b, b + n) - b;

    for (int i = 0; i < n; i++) {                     // 枚举原始数组
        int k = lower_bound(b, b + bl, a[i]) - b + 1; // 二分查找a[i]离散化后的值,注意配合树状数组+1
        /*
        状态转移方程:f[i]=a[i]+max(f[j]) (0<=j<i,a[j]<a[i])
        f[i]: f
        a[i]: a[i]
        max(f[j]):query(k - 1) 表示k-1范围内元素和最大值
        */
        LL f = a[i] + query(k - 1);
        res = max(res, f); // 更新最大值
        add(k, f);         // 更新树状数组
    }
    printf("%lld\n", res);

    // 下面的代码也可以AC
    // printf("%lld\n", query[bl]);
    return 0;
}
posted @   糖豆爸爸  阅读(150)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· Docker 太简单,K8s 太复杂?w7panel 让容器管理更轻松!
历史上的今天:
2022-06-28 AcWing 100. 增减序列
2022-06-28 AcWing 99. 激光炸弹
2022-06-28 AcWing 98. 分形之城
2021-06-28 洛谷 P1403 [AHOI2005]约数研究
2015-06-28 太经典的当爸爸过程
2013-06-28 Commons IO方便读写文件的工具类
Live2D
点击右上角即可分享
微信分享提示