翻转并乘二后的最小极差(二分答案 + 分类讨论)

翻转并乘二后的最小极差(二分答案 + 分类讨论)

在写到这道题的时候,二分思维其实已经有进步了,但是问题还是出现在二分检查的过程中,编程细节中的处理,要多写多见多悟

一、题目描述

给定一个长度为 n 的正整数数组 a。

你可以进行至多一次操作:选择一个区间 [l, r],将该区间翻转,并且区间内的所有数字乘 2。

操作后,数组变成:
[a1, ..., a_{l-1}, 2*a_r, 2*a_{r-1}, ..., 2*a_l, a_{r+1}, ..., a_n]

数组的“间隙值(Gap)”定义为相邻两数差的绝对值的最大值:max(|a_i - a_{i+1}|)。

求进行一次操作后,可能的最小间隙值。

对应题目:Codeforces Round 1133(Div. 2) Reverse and Double

二、思路分析

1. 二分答案

要求“最大值的最小可能值”,典型的二分答案特征。

我们二分间隙值的上限 x,然后写一个 check(x) 函数,判断是否存在一种操作,使得操作后的所有相邻差值都不超过 x。

若能实现,说明答案可以更小,缩小右边界;否则增大左边界。

2. 分析操作前后的相邻元素关系

假设操作前,我们找到第一个不满足 |a_i - a_{i+1}| <= x 的位置(记为 l),和最后一个不满足的位置(记为 r)。

为了把这段“违规”的区间修复,我们的操作区间必须覆盖从 l 到 r+1 的所有元素。即我们要翻转并乘二的区间必须满足 L <= l 且 R >= r + 1。

3. 分类讨论(check 函数的核心)

根据违规区间的情况,分为三类:

情况一:没有违规的地方(l == -1)

直接返回 true。

情况二:只有一个违规位置(l == r)

这种情况下,操作可以把 l 和 l+1 包含进去,并且可以向左或向右延伸,利用乘 2 后差值也可能满足条件的特性。

  • 1. 只翻转这两个数(或者以其中一个为边界):
    • 向左翻转:[l, l+1],检查 |a[l]*2 - a[l+1]| 和左边界的连接。
    • 向右翻转:[l, l+1],检查 |a[l] - a[l+1]*2| 和右边界的连接。
  • 2. 向左延伸翻转:
    向左枚举 i,如果 |a[i] - a[i+1]| * 2 <= x,说明这段区间乘 2 后可以合法。那么尝试把 [i, l+1] 翻转。检查翻转后的边界 2*a[i] 和 a[l+1],以及 2*a[l] 和左侧 a[i-1]。
  • 3. 向右延伸翻转:
    向右枚举 i,如果 |a[i] - a[i-1]| * 2 <= x,说明这段区间乘 2 后可以合法。尝试翻转 [l, i]。检查边界 a[l] 和 2*a[i],以及 2*a[l+1] 和右侧 a[i+1]。

情况三:有多个违规位置(l < r)

如果违规的位置不止一处,那么我们的操作区间必须同时覆盖这所有的违规位置。

因此,翻转区间的左端点 L 最大只能到 l,右端点 R 最小必须到 r+1。为了让操作后的边界最容易满足条件,我们贪心地选择:

  • 左端点定在 l
  • 右端点定在 r + 1

翻转后,边缘的配对是:

  • a[l] 与 2*a[r] 相连
  • 2*a[l+1] 与 a[r+1] 相连
  • 中间的部分 a[i] 与 a[i+1] 翻转后变成 2*a[i+1] 与 2*a[i],差值变为原来的 2 倍。

所以需要满足:

  1. |a[l] - a[r]*2| <= x
  2. |a[l+1]*2 - a[r+1]| <= x
  3. 对于中间所有 i \in [l+1, r-1],有 |a[i] - a[i+1]| * 2 <= x。

这三个条件全部满足,则返回 true,否则 false。

三、AC 代码

#include<bits/stdc++.h>
using namespace std;
const int N = 3e5 + 5;
int a[N];
int n;

bool check(int x)
{
    int l = -1, r = -1;
    // 找到第一处和最后一处不满足差值 <= x 的地方
    for (int i = 1; i < n; i ++)
    {
        int ans = a[i] - a[i + 1];
        if (abs(ans) > x) 
        {
            if (l == -1) l = i;
            r = i;
        }
    }
    
    // 1. 没有违规的地方
    if (l == -1) return true;
    
    // 2. 只有一处违规
    if (l == r)
    {
        // 试探 1:只翻转 l 和 l+1
        if (abs(a[l] * 2 - a[l + 1]) <= x && (l - 1 < 1 || abs(a[l - 1] - 2 * a[l]) <= x)) return true;
        if (abs(a[l] - 2 * a[l + 1]) <= x && (l + 2 > n || abs(a[l + 1] * 2 - a[l + 2]) <= x)) return true;
        
        // 试探 2:向左延伸
        bool flag = true; 
        for (int i = l - 1; i >= 1 && flag; i --)
        {
            if (abs(a[i] - a[i + 1]) * 2 <= x)
            {
                if (abs(a[i] * 2 - a[l + 1]) <= x && (i - 1 < 1 || abs(a[i - 1] - 2 * a[l]) <= x)) return true;
            }
            else flag = false;
        }
        
        // 试探 3:向右延伸
        flag = true;
        for (int i = l + 2; i <= n && flag; i ++)
        {
            if (abs(a[i] - a[i - 1]) * 2 <= x)
            {
                if (abs(a[l] - a[i] * 2) <= x && (i + 1 > n || abs(a[l + 1] * 2 - a[i + 1]) <= x)) return true;
            }
            else flag = false;
        }
        return false;
    }
    
    // 3. 有多处违规
    if (abs(a[l] - a[r] * 2) > x || abs(a[l + 1] * 2 - a[r + 1]) > x) return false;
    for (int i = l + 1; i < r; i ++)
    {
        if (abs(a[i] - a[i + 1]) * 2 > x) return false;
    }
    return true;
}

int main()
{
    int t;
    cin >> t;
    while (t --)
    {
        cin >> n;
        for (int i = 1; i <= n; i ++) cin >> a[i];
        
        // 二分的上界是原数组的最大差值
        int low = 0, high = 0;
        for (int i = 1; i < n; i ++) high = max(high, abs(a[i] - a[i + 1]));
        int ans = high;
        
        while (low <= high)
        {
            int mid = (low + high) >> 1;
            if (check(mid)) ans = mid, high = mid - 1;
            else low = mid + 1;
        }
        cout << ans << endl;
    }
    return 0;
}

四、复杂度分析

  • 时间复杂度:每次 check 函数复杂度为 O(n)。外层二分查找范围是 [0, 2*10^6],最多执行 log(10^6) ≈ 20 次。总复杂度 O(20 * n)。对于 n 总和 3*10^5 来说非常快。
  • 空间复杂度:O(n),只需存储原数组。

五、易错点

  • if (abs(a[i] * 2 - a[l + 1]) <= x && (i - 1 < 1 || abs(a[i - 1] - 2 * a[l]) <= x)) return true;
    等语句括号搞混了 写成))<= x) 找半天没找出来
  • 在边界的时候,下标也容易搞错

六、总结

这道题是一道非常好的二分 + 思维的题目。
通过二分将求解最小化问题转化为判定性问题。在判定时,通过找到首尾两个违规点,巧妙地将问题转化为“选择哪一段区间翻转”的分类讨论。虽然代码中的分类逻辑较多,但只要理清了左边界、右边界和延伸时的合并方式,整体思路十分清晰。

posted @ 2026-11-12 11:55  leaderling  阅读(4)  评论(0)    收藏  举报