#P2939. 第2题-小美的陡峭值操作
          
                        
                                    
                      
        
              - 
          
          
                      1000ms
            
          
                      Tried: 94
            Accepted: 27
            Difficulty: 6
            
          
          
          
                       所属公司 : 
                              美团
                                
            
                        
              时间 :2025年5月10日-技术岗
                              
                      
          
 
- 
                        算法标签>差分数组          
 
第2题-小美的陡峭值操作
题解
题目描述
给定长度为 n 的整型数组 {ai}i=1n,定义数组的“陡峭值”为
S=i=2∑nai−ai−1.现在允许对数组最多进行一次操作:选择一个区间 [l,r],将区间内所有元素加 1。
问:在进行该操作(也可以不操作)的情况下,数组的陡峭值最小是多少?
思路
- 
初始陡峭值
计算不做任何操作时的陡峭值
S = sumi=2n∣di∣,di = ai - ai−1 - 
操作影响分析
若对区间 [l,r] 全部加 1,则只有与区间边界相邻的两条差分可能变化:- 边界左侧:位置 i=l 时,原来贡献 ∣al−al−1∣ 变为 ∣al+1−al−1∣;
 - 边界右侧:位置 i=r+1 (若 r<n)时,原来贡献 ∣ar+1−ar∣ 变为 ∣ar+1−(ar+1)∣。
 
记

表示“左边界加 1”带来的陡峭值减少量;

表示“右边界加 1”带来的陡峭值减少量。则对区间 [l,r] 的总减少量为
Δ(l,r)=gl+hr.我们要选取 l≤r 使得 Δ(l,r) 最大,从而使最终陡峭值

最小。 - 
“一次扫描”求最优
- 预先计算 gi 和 hi 数组,时间 O(n);
 - 维护前缀最大值prefMaxi=1≤j≤imaxgj, 然后枚举 r=1…n,对于每个 r,最优的 l≤r 时的减少量为prefMaxr+hr.
 - 在枚举过程中取最大即可,整体仍是 O(n)。
 
 
cpp
#include <bits/stdc++.h>
using namespace std;
int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    int t;
    cin >> t;
    while (t--) {
        int n;
        cin >> n;
        vector<long long> a(n+2);
        for (int i = 1; i <= n; i++) {
            cin >> a[i];
        }
        // 计算初始陡峭值 S
        long long S = 0;
        for (int i = 2; i <= n; i++) {
            S += llabs(a[i] - a[i-1]);
        }
        // 辅助数组 g 和 h
        vector<long long> g(n+1, 0), h(n+2, 0);
        // g[i] = |a[i]-a[i-1]| - |a[i]+1 - a[i-1]|
        for (int i = 2; i <= n; i++) {
            long long oldv = llabs(a[i] - a[i-1]);
            long long newv = llabs((a[i] + 1) - a[i-1]);
            g[i] = oldv - newv;
        }
        // h[i] = |a[i+1]-a[i]| - |a[i+1] - (a[i]+1)|
        for (int i = 1; i < n; i++) {
            long long oldv = llabs(a[i+1] - a[i]);
            long long newv = llabs(a[i+1] - (a[i] + 1));
            h[i] = oldv - newv;
        }
        // 前缀最大 g
        vector<long long> prefMax(n+1, LLONG_MIN);
        prefMax[1] = g[1];  // g[1] = 0
        for (int i = 2; i <= n; i++) {
            prefMax[i] = max(prefMax[i-1], g[i]);
        }
        // 枚举 r,寻找最大减少量
        long long bestGain = 0;
        for (int r = 1; r <= n; r++) {
            // prefMax[r] + h[r]
            bestGain = max(bestGain, prefMax[r] + h[r]);
        }
        // 答案 = S - bestGain
        cout << (S - bestGain) << "\n";
    }
    return 0;
}
python
# Python3 版本
import sys
input = sys.stdin.readline
def solve():
    t = int(input())
    for _ in range(t):
        n = int(input())
        a = list(map(int, input().split()))
        # 初始陡峭值
        S = sum(abs(a[i] - a[i-1]) for i in range(1, n))
        # 计算 g, h
        g = [0]*n
        h = [0]*n
        # g[i]: 左边界减少
        for i in range(1, n):
            old = abs(a[i] - a[i-1])
            new = abs(a[i] + 1 - a[i-1])
            g[i] = old - new
        # h[i]: 右边界减少
        for i in range(n-1):
            old = abs(a[i+1] - a[i])
            new = abs(a[i+1] - (a[i] + 1))
            h[i] = old - new
        # 前缀最大 g
        pref = [0]*n
        pref[0] = g[0]
        for i in range(1, n):
            pref[i] = max(pref[i-1], g[i])
        # 枚举 r 寻最优
        best = 0
        for r in range(n):
            best = max(best, pref[r] + h[r])
        print(S - best)
if __name__ == "__main__":
    solve()
Java
// Java 版本
import java.io.*;
import java.util.*;
public class Main {
    public static void main(String[] args) throws IOException {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        int t = Integer.parseInt(br.readLine());
        while (t-- > 0) {
            int n = Integer.parseInt(br.readLine());
            long[] a = new long[n+1];
            StringTokenizer st = new StringTokenizer(br.readLine());
            for (int i = 1; i <= n; i++) {
                a[i] = Long.parseLong(st.nextToken());
            }
            // 计算初始陡峭值
            long S = 0;
            for (int i = 2; i <= n; i++) {
                S += Math.abs(a[i] - a[i-1]);
            }
            // 计算 g 和 h 数组
            long[] g = new long[n+1];
            long[] h = new long[n+2];
            // 左边界
            for (int i = 2; i <= n; i++) {
                long oldv = Math.abs(a[i] - a[i-1]);
                long newv = Math.abs((a[i] + 1) - a[i-1]);
                g[i] = oldv - newv;
            }
            // 右边界
            for (int i = 1; i < n; i++) {
                long oldv = Math.abs(a[i+1] - a[i]);
                long newv = Math.abs(a[i+1] - (a[i] + 1));
                h[i] = oldv - newv;
            }
            // 前缀最大 g
            long[] prefMax = new long[n+1];
            prefMax[1] = g[1];
            for (int i = 2; i <= n; i++) {
                prefMax[i] = Math.max(prefMax[i-1], g[i]);
            }
            // 枚举 r 找最优
            long bestGain = 0;
            for (int r = 1; r <= n; r++) {
                bestGain = Math.max(bestGain, prefMax[r] + h[r]);
            }
            // 输出结果
            System.out.println(S - bestGain);
        }
    }
}
        题目内容
定义一个数组的的陡峭值为:相邻两个元素之差的绝对值之和。
现在小美拿到了一个数组,她可以最多进行1次操作:选择一个区间,使得区间内所有元素加1。
小美希望最终数组的陡峭值尽可能小,你能帮帮她吗?
输入描述
第一行输入一个正整数t,代表询问次数。
对于每次询问输入两行:
第一行输入一个正整数n,代表数组长度。
第二行输入n个正整数ai,代表小美拿到的数组。
1≤t≤1000
2≤n≤105
1≤ai≤109
保证所有询问的n,的总和不超过105
输出描述
输出t行,输出一个整数,代表该次查询陡峭值的最小值。
样例1
输入
2
5
1 4 2 3 4
3 
1 2 1
输出
5
1
说明
第一组询问,选择[3,4]区间即可,数组变成{1,4,3,4,4}。
第二组询问,选择[1,1]区间即可,数组变成{2,2,1}。