「做题记录」洛谷 P8563 Magenta Potion (线段树维护/线性贪心)

博客讲述了如何解决P8563 Magenta Potion问题,提供了线段树和线性贪心两种解法。线段树方法用于维护区间负数位置、个数和乘积,通过二分查找优化查询效率。线性贪心方法通过判断区间长度来简化计算,当长度超过一定阈值时,直接得出最大乘积。博主分享了赛时因误解题目而导致的失误,提醒读者仔细阅读题目。
摘要由CSDN通过智能技术生成

比赛时做到的题,感觉挺有意思的,就写了一下。

P8563 Magenta Potion 题面

解题思路

前言

我打算在这篇题解中将线段树做法和线性贪心做法都写出来,以拓宽大家的思路,让大家有所收获。

ps:线段树篇幅略长,想直接看简单的贪心可以直接跳过。


解题思路

先抛开修改操作,思考一下如何计算区间的最大乘积。

因为 ∣ k ∣ , ∣ a i ∣ ≥ 2 \left | k \right |,\left | a_i \right | \ge 2 k,ai2,那么在保证乘积为正数时,乘的数字越多,乘积越大。

由于负数的特殊性,所以要根据负数的个数进行分类讨论。

  • 当负数的个数为偶数时:

    就可以取整个区间,答案就是整个区间的乘积。

  • 当负数的个数为 1 1 1 且区间只有一个数字时:

    答案就为 1 1 1

  • 当负数的个数为 1 1 1 且区间有大于 1 1 1 个数字时:

    答案为负数位置左侧的乘积和右侧的乘积中的较大值。

  • 当负数的个数为奇数时:

    此时就需要舍弃一个负数。根据上面的说法,就需要舍弃最左边的负数,或者最右边的负数,将剩余连续的偶数个负数和其余的正数相乘,两种方案取最大值,就是最终答案。

Solution1 \text{Solution1} Solution1

根据以上思路,我们需要维护的区间内的值有这些:负数出现的位置,负数的个数,区间乘积。

这启发我们采用线段树进行操作。

在查询区间最左边的负数位置时,可以用二分找出最小的前缀负数个数为 1 1 1 的位置,查询区间最右边的负数位置时,也是如此。时间复杂度为 O ( log ⁡ 2 n ) O(\log^2n) O(log2n)

由于是单点修改,那么就不需要使用懒标记维护区间乘积了,可以直接维护。查询和修改的时间复杂度均为 O ( log ⁡ n ) O(\log n) O(logn)

查询最终答案时,根据上面思路直接写,时间复杂度 O ( log ⁡ n ) O(\log n) O(logn)

综上,时间复杂度为 O ( n log ⁡ 2 n ) O(n \log^2 n) O(nlog2n)

值得一提的是,根据数据范围发现,如果直接维护区间乘积,这个值是非常非常巨大的,并且无法存放。

那我们可以这样操作:由于负数在最终乘完后也变为正数,那么在计算的时候判断运算中的值的绝对值是否大于等于 2 30 + 1 2^{30} + 1 230+1,如果符合,那就将其修改为 2 30 + 1 2^{30}+1 230+1。这样做减小了数据规模,并且最终计算得到的结果也会大于 2 30 2^{30} 230 ,不影响正确性。这样就可以存储下来了。

给出较简洁且部分注释的代码:

#include <bits/stdc++.h>

using namespace std;
typedef long long ll;

const int N = 2e5 + 7;
const ll P = (1ll << 30) + 1;

int n, q;
ll a[N], sum[N << 2]; //sum维护区间乘积
int num[N << 2], pos[N << 2];//num维护区间负数个数,pos维护区间负数位置

#define _max(a, b)<
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值