小美的陡峭值操作

[题目链接](https://www.nowcoder.com/practice/a4d34a7d8fb145af85f7190ee91dddf3)

思路

定义数组的陡峭值为相邻元素之差的绝对值之和:。可以最多进行一次操作:选一个区间 ,将区间内所有元素加 1。求操作后陡峭值的最小值。

操作对差分的影响

设差分 ,则陡峭值

对区间 整体加 1,只影响区间边界处的两个差分值:

  • 增加 1(左边界外侧和内侧的差距变大)
  • 减少 1(右边界内侧和外侧的差距变小)

区间内部的差分完全不变。因此问题转化为:选两个位置 (分别对应 ),使得 相比 的减少量最大。也可以只用一侧边界()。

贪心扫描

定义两个收益函数:

  • 左边界收益:,表示将 加 1 后绝对值减少了多少
  • 右边界收益:,表示将 减 1 后绝对值减少了多少

最大总收益为以下四种情况的最大值:

  1. 不操作:收益 0
  2. 只用右边界():
  3. 只用左边界():
  4. 同时用两侧():

对于情况 4,从左到右扫描时维护 的前缀最大值,即可在 时间内求解。

样例演示

样例 1,差分 ,初始陡峭值

(即操作区间 ):收益 。陡峭值

样例 2,差分 ,初始陡峭值

只用右边界 (即操作 ):收益 。陡峭值

复杂度分析

  • 时间复杂度:,单次扫描差分数组。
  • 空间复杂度:,存储差分数组。

代码

#include <bits/stdc++.h>
using namespace std;

int main(){
    int T;
    scanf("%d", &T);
    while(T--){
        int n;
        scanf("%d", &n);
        vector<long long> a(n);
        for(int i = 0; i < n; i++) scanf("%lld", &a[i]);
        if(n == 1){
            printf("0\n");
            continue;
        }
        vector<long long> d(n - 1);
        long long base = 0;
        for(int i = 0; i < n - 1; i++){
            d[i] = a[i + 1] - a[i];
            base += abs(d[i]);
        }
        long long best = 0;
        long long maxLeft = LLONG_MIN;
        for(int i = 0; i < n - 1; i++){
            long long rg = abs(d[i]) - abs(d[i] - 1);
            if(maxLeft != LLONG_MIN){
                best = max(best, maxLeft + rg);
            }
            best = max(best, rg);
            long long lg = abs(d[i]) - abs(d[i] + 1);
            best = max(best, lg);
            maxLeft = max(maxLeft, lg);
        }
        printf("%lld\n", base - best);
    }
    return 0;
}
import java.util.*;
import java.io.*;

public class Main {
    public static void main(String[] args) throws Exception {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        int T = Integer.parseInt(br.readLine().trim());
        StringBuilder sb = new StringBuilder();
        while (T-- > 0) {
            int n = Integer.parseInt(br.readLine().trim());
            StringTokenizer st = new StringTokenizer(br.readLine().trim());
            long[] a = new long[n];
            for (int i = 0; i < n; i++) a[i] = Long.parseLong(st.nextToken());
            if (n == 1) {
                sb.append(0).append('\n');
                continue;
            }
            long[] d = new long[n - 1];
            long base = 0;
            for (int i = 0; i < n - 1; i++) {
                d[i] = a[i + 1] - a[i];
                base += Math.abs(d[i]);
            }
            long best = 0;
            long maxLeft = Long.MIN_VALUE;
            for (int i = 0; i < n - 1; i++) {
                long rg = Math.abs(d[i]) - Math.abs(d[i] - 1);
                if (maxLeft != Long.MIN_VALUE) {
                    best = Math.max(best, maxLeft + rg);
                }
                best = Math.max(best, rg);
                long lg = Math.abs(d[i]) - Math.abs(d[i] + 1);
                best = Math.max(best, lg);
                maxLeft = Math.max(maxLeft, lg);
            }
            sb.append(base - best).append('\n');
        }
        System.out.print(sb);
    }
}