单调栈+前缀和+二分,CF1998 E2 - Eliminating Balls With Merging (Hard Version)

一、题目

1、题目描述

2、输入输出

2.1输入

2.2输出

3、原题链接

E2 - Eliminating Balls With Merging (Hard Version)


二、解题报告

1、思路分析

前缀和+单调栈,CF1998 E1 - Eliminating Balls With Merging (Easy Version)-CSDN博客

和E1同样是单调栈预处理pre[i],suf[i],代表每个下标左边右边第一个大于自己的

然后还是将下标索引按照值排序

然后从大到小处理每个下标

当处理下标i时,如果pre[i] 和 suf[i]存在,那么他们因为值大,已经被提前处理了

我们只需判断 i 是否能与其合并,所以我们开两个数组start[i] end[i] 记录每个 i 可以被保留的区间(左闭右开)

我们二分查找b,b 是满足 acc[b] - acc[pre[i] + 1] >= a[pre[i]] 的最小下标(b >= i + 1)

下面分情况讨论:

如果 b <= suf[i]:

        如果pre[i] 存在,那么我们左合并,start[i] = max(start[pre[i]], b - 1),end[i] = end[pre[i]]

        如果pre[i] 不存在,说明 a[i] 是前缀最值,我们只需看是否能右合并

        start[i] = b - 1,end[i] = s < a[suf[i]] ? suf[i] : end[suf[i]]

否则,如果b > suf[i]

        如果suf[i]不存在,或者 不能右合并

                那么 start[i] = i, end[i] = suf[i]

               否则,我们右合并,start[i] = start[suf[i]],end[i] = end[suf[i]]

然后计算出区间的时候对差分数组修改,最后遍历差分数组求和就行了

对于有些DSU做法真的看不懂……

2、复杂度

时间复杂度: O(NlogN)空间复杂度:O(N)

3、代码详解

 ​
#include <bits/stdc++.h>
#include <ranges>
// #define DEBUG
using i64 = long long;
using u32 = unsigned;
using u64 = unsigned long long;
constexpr int inf32 = 1E9 + 7;
constexpr i64 inf64 = 1E18 + 7;
constexpr double eps = 1E-9;

void solve() {
    int n, x;
    std::cin >> n >> x;
    std::vector<int> a(n), st, pre(n, -1), suf(n, n), start(n), end(n);
    std::vector<i64> acc(n + 1);
    
    for (int i = 0; i < n; ++ i) {
        std::cin >> a[i];
        acc[i + 1] = acc[i] + a[i];
        while (st.size() && a[i] > a[st.back()]) {
            suf[st.back()] = i;
            st.pop_back();
        }
        st.push_back(i);
    }

    st.clear();

    for (int i = n - 1; ~i; -- i) {
        while (st.size() && a[i] > a[st.back()]) {
            pre[st.back()] = i;
            st.pop_back();
        }
        st.push_back(i);
    }

    std::vector<int> id(n);
    std::iota(id.begin(), id.end(), 0);
    std::ranges::sort(id, [&](int i, int j) -> bool {
        return a[i] > a[j];
    });

    std::vector<int> dif(n + 1);

    auto get = [&](int i, int lo, int hi) -> int {
        while (lo < hi) {
            int x = lo + hi >> 1;
            if (acc[x] - acc[pre[i] + 1] - (~pre[i] ? a[pre[i]] : 0) >= 0)
                hi = x;
            else
                lo = x + 1;
        }
        return lo;
    };

    for (int i : id) {
        if (pre[i] == -1 && suf[i] == n) {
            ++ dif[start[i] = i];
            -- dif[end[i] = n];
            continue;
        }

        i64 s = acc[suf[i]] - acc[pre[i] + 1];
        auto b = get(i, i + 1, suf[i] + 1); // 前缀和下标为b,实际为b - 1

        if (b <= suf[i]) {
            if (pre[i] >= 0) {
                ++ dif[start[i] = std::max(start[pre[i]], b - 1)];
                -- dif[end[i] = end[pre[i]]];
            } else {
                ++ dif[start[i] = b - 1];
                -- dif[end[i] = s < a[suf[i]] ? suf[i] : end[suf[i]]];
            }
        } else if (suf[i] == n || (suf[i] < n && s < a[suf[i]])) {
            ++ dif[start[i] = b - 1];
            -- dif[end[i] = suf[i]];
        } else {
            ++ dif[start[i] = start[suf[i]]];
            -- dif[end[i] = end[suf[i]]];
        }
    }

    for (int i = 0, s = 0; i < n; ++ i) {
        s += dif[i];
        std::cout << s << " \n"[i + 1 == n];
    }
}

auto FIO = []{
    std::ios::sync_with_stdio(false);
    std::cin.tie(nullptr);
    std::cout.tie(nullptr);
    return 0;
} ();

int main() {
    #ifdef DEBUG
        freopen("in.txt", "r", stdin);
        freopen("out.txt", "w", stdout);
    #endif     

    int t = 1;
    std::cin >> t;
    while (t --)
        solve();

    return 0;
}

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

EQUINOX1

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

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

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

打赏作者

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

抵扣说明:

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

余额充值