有点意思的前缀和:非零段划分-CSP202109-2

题目描述

202109-2

解题思路

一种麻烦的假优化
  • 记得在考场上应该是暴力拿了70分就走了
  • 下来自己慢慢做的时候第一反应是不知道从何下手,经过“几番斟酌”,觉得可以借鉴线段树懒惰标记的想法,以“非零段”为基本单位进行遍历,复杂度是 O ( p × 最 大 非 零 段 数 ) O(p\times 最大非零段数) O(p×),以为不会超时了。双链表写的很麻烦结果还是寄了,见实现一。
有点意思的前缀和
  • 后来参考了前缀和以及差分这篇博文才意识到这可以是一个前缀和的题。
  • 这个题的前缀和可能没有那么直观,上述博文中认为这种思路是差分,我觉得类比扫描线理解可能更直观一些:首先需要比较直观的观察,建议考场画图看,也就是说只有 A [ i − 1 ] < A [ i ] A[i-1]<A[i] A[i1]<A[i] 的情况才有可能产生一个非零段,产生的条件是 p p p 落在 A [ i − 1 ] A[i-1] A[i1] A [ i ] A[i] A[i] 中间。思路的巧妙之处是不需要考虑下降段,也就是说使用上升段代替了整个非零段进行计数;对于每一组 A [ i − 1 ] < A [ i ] A[i-1]<A[i] A[i1]<A[i] ,可以使用一个大数组 d i f f diff diff 记录两个端点,即 d i f f [ A [ i − 1 ] + 1 ] + + , d i f f [ A [ i ] + 1 ] − − diff[A[i-1]+1]++, diff[A[i]+1]-- diff[A[i1]+1]++,diff[A[i]+1]。也就是说,当 p p p 到达 d i f f [ A [ i − 1 ] + 1 ] diff[A[i-1]+1] diff[A[i1]+1] 的时候,非零段数目增加 1 1 1,到达 d i f f [ A [ i ] + 1 ] diff[A[i]+1] diff[A[i]+1] 的时候,非零段的数目减少 1 1 1。最终 p p p 时的非零段数目即是 ∑ i = 1 p d i f f [ i ] \sum_{i=1}^p diff[i] i=1pdiff[i] 的结果。
  • 需要注意的细节是,运行算法之前需要对原数组 A A A 收尾各添一个 0 0 0 作为起始点。

代码实现

  • 实现一:70分,以非零段为单位的处理
#include <iostream>
#include <vector>
#include <algorithm>

#define SEG_OUT 0
#define SEG_IN 1
struct None_zero_seg {
    None_zero_seg(None_zero_seg * nxt, None_zero_seg * pre, int l, int r, int lst, int lz) : next(nxt), prev(pre), left(l), right(r), least(lst), lazy(lz) {}
    None_zero_seg * next;
    None_zero_seg * prev;
    int left, right;
    int least;
    int lazy;
};

struct None_zero_segs {
    None_zero_segs() : head(nullptr), tail(nullptr), total(0) {}
    None_zero_seg * head;
    None_zero_seg * tail;
    int total;
};

using namespace std;
int main() {
    int n;
    scanf("%d", &n);
    int A[n+2];
    A[0] = A[n+1] = 0;
    None_zero_segs Nzs;
    Nzs.head = new None_zero_seg(nullptr, nullptr, 0, 0, 0, 0);
    Nzs.tail = Nzs.head;
    bool state = SEG_OUT;
    int left, seg_min, all_max = 0;
    for (int i = 1; i <= n; i++) scanf("%d", &A[i]);
    for (int i = 1; i <= n+1; i++) {
        all_max = max(all_max, A[i]);
        if (state == SEG_OUT && A[i] != 0) {
            state = SEG_IN;
            left = i;
            seg_min = A[i];
        }
        else if (A[i] != 0) seg_min = min(seg_min, A[i]);
        else if (state == SEG_IN && A[i] == 0) {
            state = SEG_OUT;
            auto *new_seg = new None_zero_seg(nullptr, Nzs.tail, left, i-1, seg_min, 0);    // Mark
            Nzs.total++;
            Nzs.tail->next = new_seg;
            Nzs.tail = new_seg;
        }
        else ;
    }
    int total_max = Nzs.total;
    for (int p = 1; p <= all_max; p++) {
        for (None_zero_seg * seg = Nzs.head->next; seg != nullptr; seg = seg->next) { // 注意如果拆了之后要seg = 拆了之后的最后一个元素
            if (seg->least >= p) continue;
            Nzs.total--;
            None_zero_seg * tmp_tail = seg->prev;
            state = SEG_OUT;
            for (int i = seg->left; i <= seg->right; i++) {
                if (state == SEG_OUT && A[i] >= p) {
                    state = SEG_IN;
                    left = i;
                    seg_min = A[i];
                }
                if (state == SEG_IN && (A[i] < p || i == seg->right)) { // 注意不是else if,不然边界条件无法收尾
                    state = SEG_OUT;
                    auto *new_seg = new None_zero_seg(nullptr, tmp_tail, left, i == seg->right ? i : i-1, seg_min, 0);    // Mark
                    Nzs.total++;
                    tmp_tail->next = new_seg;
                    tmp_tail = new_seg;
                }
                if (A[i] >= p) seg_min = min(seg_min, A[i]);
                if (A[i] < p) A[i] = 0;
            }
            tmp_tail->next = seg->next;
            if (seg->next != nullptr) seg->next->prev = tmp_tail;
            delete seg;
            seg = tmp_tail;
        }
        total_max = max(total_max, Nzs.total);
    }
    printf("%d", total_max);
    return 0;
}
  • 实现二:100分,基于前缀和的巧妙处理
#include <iostream>

#define MAX_n 1000000
#define MAX_N 10000
int diff[MAX_N];
int A[MAX_n];

using namespace std;

int main() {
    int n;
    scanf("%d", &n);

    for (int i = 1; i <= n; i++) scanf("%d", &A[i]);
    for (int i = 0; i <= n+1; i++) {
        if (A[i] > A[i-1]) {
            diff[A[i-1]+1]++;
            diff[A[i]+1]--;
        }
    }

    int total_max = 0, prefix = 0;
    for (int p = 1; p < MAX_N; p++) {
        prefix += diff[p];
        total_max = max(prefix, total_max);
    }
    printf("%d", total_max);
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值