【一百零九】【算法分析与设计】树状数组求解前缀最大值,673. 最长递增子序列的个数,树状数组求前缀区间最大值

树状数组求解前缀最大值

树状数组可以求解和前缀区间有关的问题,例如前缀和,前缀区间最值.
可以利用 l o g n log_n logn的时间复杂度快速查找前缀信息.
利用树状数组查询前缀区间中最大值问题.
在这里插入图片描述
树状数组下标1位置存储arr数组下标1位置的最大值.
树状数组2位置存储arr数组1,2位置最大值.
数组数组3位置存储arr数组3位置最大值.
以此类推…
查找arr数组1~i前缀区间最大值.利用lowbit跳转,记录遍历的每一个tree[i]的最大值.

lowbit跳转,while(i<tree.size()),i+=lowbit(i),i会遍历所有拥有i位置的数组数组位置.
while(i>0)i-=lowbit(i),i会遍历所有能够组成1,2,3…i的树状数组位置.
在这里插入图片描述
如果i位置是7,lowbit往前跳转,树状数组会遍历4,6,7位置,对应arr组成1,2,3,4,5,6,7.
往后跳转,树状数组会遍历7,8,拥有arr中7位置的树状数组位置.

#include<bits/stdc++.h>
using namespace std;

#define int long long // 定义 int 为 long long 类型,方便处理大数
#define endl '\n' // 定义 endl 为换行符,方便输出

vector<int> arr = { 1, 4, 2, 5, 3, 5, 6, 3, 2, 5 }; // 定义一个全局数组并初始化

// 树状数组类定义
class Tree {
public:
    vector<int> tree; // 定义一个向量 tree,用于存储树状数组

    // 计算 lowbit
    int lowbit(int i) {
        return i & -i; // 返回 i 和 -i 的按位与,获取最低位的 1
    }

    // 在位置 i 处更新值 v
    void sett(int i, int v) {
        while (i <= tree.size()) { // 从索引 i 开始,向上更新树状数组
            tree[i] = max(tree[i], v); // 更新节点为当前值和新值的最大值
            i += lowbit(i); // 移动到下一个需要更新的位置
        }
    }

    // 查询前缀 [1, i] 的最大值
    int gett(int i) {
        int ret = LLONG_MIN; // 初始化结果为负无穷大
        while (i > 0) { // 从索引 i 开始,向下计算前缀最大值
            ret = max(tree[i], ret); // 更新结果为当前值和当前结果的最大值
            i -= lowbit(i); // 移动到下一个需要计算的位置
        }
        return ret; // 返回前缀最大值
    }

    // 默认构造函数
    Tree() {}

    // 使用给定大小 n 初始化树状数组
    Tree(int n) {
        tree.assign(n + 3, 0); // 初始化树状数组大小,并将值设为 0
    }

    // 使用给定数组初始化树状数组
    Tree(vector<int> arr) {
        tree.assign(arr.size() + 3, LLONG_MIN); // 初始化树状数组大小,并将值设为负无穷大
        int i = 1;
        for (auto& xx : arr) {
            sett(i++, xx); // 将数组中的值添加到树状数组中
        }
    }
};

Tree t1(arr); // 使用全局数组初始化树状数组 t1
int q; // 定义查询次数

// 主解题函数
void solve() {
    for (auto& xx : arr) {
        cout << xx << " "; // 输出数组中的每个元素
    }
    cout << endl;
    for (int i = 0; i < arr.size(); i++) {
        cout << i << " "; // 输出数组的索引
    }
    cout << endl;
    cin >> q; // 读取查询次数
    for (int i = 1; i <= q; i++) {
        int k;
        cin >> k; // 读取查询索引
        k++;
        cout << t1.gett(k) << endl; // 输出前缀 [1, k] 的最大值
    }
}

signed main() {
    ios::sync_with_stdio(0), cin.tie(0), cout.tie(0); // 快速输入输出

    solve(); // 调用解题函数
}

673. 最长递增子序列的个数

给定一个未排序的整数数组 nums , 返回最长递增子序列的个数 。

注意 这个数列必须是 严格 递增的。

示例 1:

输入: [1,3,5,4,7]
输出: 2
解释: 有两个最长递增子序列,分别是 [1, 3, 4, 7] 和[1, 3, 5, 7]。
示例 2:

输入: [2,2,2,2,2]
输出: 5
解释: 最长递增子序列的长度是1,并且存在5个子序列的长度为1,因此输出5。

提示:

1 < = n u m s . l e n g t h < = 2000 1 <= nums.length <= 2000 1<=nums.length<=2000
− 1 0 6 < = n u m s [ i ] < = 1 0 6 -10^6 <= nums[i] <= 10^6 106<=nums[i]<=106

countt数组,对应arr数组每一个元素.
countt存储node类型,node类型有两个变量,maxlen和count.
countt[i].maxlen表示arr数组中以i位置元素为结尾的最长递增子序列的长度,countt[i].count表示arr数组中以i位置元素为结尾的最长递增子序列的个数.
如果countt数组维护完,只需要直到最长递增子序列的长度,然后遍历countt数组,统计是最长递增子序列长度的个数即可.

填写countt[i]位置的数据,需要直到1~i-1区间中,元素值小于arr[i]的最长递增子序列的长度,以及个数.
元素值小于arr[i],对应词频表,元素值映射个数,词频.元素值作为下标,刚好是排序好的,也就是求前缀和.
1~i-1,对应依次遍历.
利用树状数组存储以某元素值为结尾的最长递增子序列的长度,元素值映射最长的长度.
在这里插入图片描述
index映射arr,元素值映射index,index映射maxlen.

利用map做离散化处理.
利用树状数组查询1~i-1以小于arr[i]元素结尾的最长递增子序列的长度.
然后遍历arr数组,统计数量.维护当前位置countt.

Tree中gett函数获取1~i最大值,但是如果进来的i是0,直接返回0,最大长度不存在.因为外面要用这个最大长度+1表示当前的最大长度.

struct node {
    int maxlen = 1, count = 0; // 定义节点结构,包含最大长度和计数
};

class Tree {
public:
    vector<int> tree; // 定义一个向量 tree,用于存储树状数组

    // 计算 lowbit
    int lowbit(int i) { 
        return i & -i; // 返回 i 和 -i 的按位与,获取最低位的 1
    }

    // 在位置 i 处更新值 v
    void sett(int i, int v) {
        while (i < tree.size()) { // 从索引 i 开始,向上更新树状数组
            tree[i] = max(tree[i], v); // 更新节点为当前值和新值的最大值
            i += lowbit(i); // 移动到下一个需要更新的位置
        }
    }

    // 查询前缀 [1, i] 的最大值
    int gett(int i) {
        int ret = INT_MIN; // 初始化结果为负无穷大
        if (i == 0) // 如果索引为 0
            return 0; // 返回 0
        while (i > 0) { // 从索引 i 开始,向下计算前缀最大值
            ret = max(ret, tree[i]); // 更新结果为当前值和当前结果的最大值
            i -= lowbit(i); // 移动到下一个需要计算的位置
        }
        return ret; // 返回前缀最大值
    }

    // 默认构造函数
    Tree() {}
};

class Solution {
public:
    Tree t1; // 定义一个树状数组对象 t1
    vector<int> arr; // 定义一个向量 arr,用于存储输入的数组
    map<int, int> arr_index; // 定义一个 map,用于存储数组元素的索引
    vector<node> countt; // 定义一个向量 countt,用于存储每个位置的节点信息
    int ret = 0; // 定义一个变量 ret,用于存储结果
    int maxnlen = 0; // 定义一个变量 maxnlen,用于存储最大长度

    // 主解题函数
    void solve() {
        maxnlen = 0; // 初始化最大长度为 0
        ret = 0; // 初始化结果为 0
        arr_index.clear(); // 清空索引 map
        for (auto& x : arr) { // 遍历输入数组
            arr_index[x]; // 在 map 中记录每个元素
        }
        int index = 1;
        for (auto& x : arr_index) { // 给每个元素分配唯一的索引
            x.second = index++;
        }
        countt.assign(arr.size(), node()); // 初始化 countt 向量
        t1.tree.assign(index, 0); // 初始化树状数组
        for (int i = 0; i < arr.size(); i++) { // 遍历数组
            int index = arr_index[arr[i]]; // 获取当前元素的索引
            int maxlen = t1.gett(index - 1); // 获取当前索引之前的最大长度
            int ans = 0;
            for (int j = 0; j < i; j++) { // 遍历之前的元素
                if (countt[j].maxlen == maxlen && arr[j] < arr[i]) { // 找到符合条件的元素
                    ans += countt[j].count; // 累加计数
                }
            }
            countt[i].maxlen = maxlen + 1; // 更新当前元素的最大长度
            countt[i].count = max(ans, 1); // 更新当前元素的计数
            t1.sett(index, countt[i].maxlen); // 在树状数组中更新当前元素的最大长度
            maxnlen = max(maxnlen, countt[i].maxlen); // 更新最大长度
        }

        for (int i = 0; i < countt.size(); i++) { // 遍历 countt 向量
            if (countt[i].maxlen == maxnlen) { // 找到最大长度的元素
                ret += countt[i].count; // 累加结果
            }
        }
    }

    // 查找最长递增子序列的数量
    int findNumberOfLIS(vector<int>& _nums) {
        ios::sync_with_stdio(0), cin.tie(0), cout.tie(0); // 快速输入输出
        arr = _nums; // 将输入数组赋值给 arr
        solve(); // 调用解题函数
        return ret; // 返回结果
    }
};


结尾

最后,感谢您阅读我的文章,希望这些内容能够对您有所启发和帮助。如果您有任何问题或想要分享您的观点,请随时在评论区留言。
同时,不要忘记订阅我的博客以获取更多有趣的内容。在未来的文章中,我将继续探讨这个话题的不同方面,为您呈现更多深度和见解。
谢谢您的支持,期待与您在下一篇文章中再次相遇!

  • 43
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

妖精七七_

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值