从CF1373D看最大子段和与奇偶段的分析

Problem - 1373D - Codeforces

先看出了一个很显然的东西,逆转的子序列的长度必须是偶数。

但之后就想错了,想到双指针和其他方法去求这个最大段。

但我粗暴的通过 \(a_{i + 1} - a_i\) 来贪心双指针明显是不对的。

最大子段和

只要把 \(a_{i + 1} - a_{i}\) 转成一个数组 \(b_i\),发现问题就转化成了经典问题最大子段和

求一个序列连续段的最大和。

利用 dp 思想,设加上当前这个数之后,前缀和为 \(SUM\)

如果 \(SUM \ge 0\),那么这个前缀仍然可能贡献最大答案,加上这个数没问题。

如果 \(SUM < 0\),那么这个前缀还不如不选得到的 \(0\)\(SUM \gets 0\)

然后每一次都更新答案。

for (auto& x : a) {
	sum += x;
    if (sum < 0) {
        sum = 0;
    }
    ans = std::max(ans, sum);
}

奇偶段问题

但我们发现如果只从 \(i = 0\) 开始枚举段样例都过不了。

这是因为这类枚举奇\偶段问题要分两次枚举才能枚举完所有奇偶段

  • \(0\) 开始枚举完
  • \(1\) 开始枚举完

这是很显然的,比如:

1 3 4 5 6 7

显然我们要包括所有偶数段,如果只从 0 开始

1 3, 1 3 4 5, 1 3 4 5 6 7

少了一些段,所以还要再从 \(1\) 开始

3 4, 3 4 5 6

代码实现

void solve() {
    int n;
    std::cin >> n;
    std::vector<int> a(n);
    for (auto& x : a) std::cin >> x;
    i64 ans{};
    for (int i = 0; i < n; i++) {//先把原来偶数位上都加起来
        if (i % 2 == 0) {
            ans += a[i];
        }
    }
    
    i64 mxPre{};

    auto getMxPre = [&](auto& a) -> void {//最大子段和问题
        i64 pre{};
        i64 _mxPre{};
        for (auto& x : a) {
            pre += x;
            if (pre < 0) {
                pre = 0;
            }
            _mxPre = std::max(_mxPre, pre);
        }
        mxPre = std::max(mxPre, _mxPre);
    };

    std::vector<int> deal0, deal1;

    for (int i = 0; i + 1 < n; i += 2) {
        deal0.push_back(a[i + 1] - a[i]);
    } 

    for (int i = 1; i + 1 < n; i += 2) {
        deal1.push_back(a[i] - a[i + 1]);//这里是因为从1开始枚举每次开头的都是奇数
    }
    
    getMxPre(deal0); getMxPre(deal1);

    std::cout << ans + mxPre << '\n';
}
posted @ 2024-03-28 19:03  加固文明幻景  阅读(11)  评论(0编辑  收藏  举报