第七讲 贪心

理论

贪心算法题性质:
1、跳跃性很强
2、结论证明很难
解决办法
1、找相似
2、猜
贪心的具体含义:
1、找一个最优解
2、短视(看重眼前的局势,不会考虑后面)

AcWing 1055. 股票买卖 II

给定一个长度为 N 的数组,数组中的第 i 个数字表示一个给定股票在第 i 天的价格。

设计一个算法来计算你所能获取的最大利润。你可以尽可能地完成更多的交易(多次买卖一支股票)。

注意:你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)。

输入格式
第一行包含整数 N,表示数组长度。

第二行包含 N 个不大于 10000 的正整数,表示完整的数组。

输出格式
输出一个整数,表示最大利润。

数据范围
1≤N≤105
输入样例1:
6
7 1 5 3 6 4
输出样例1:
7
输入样例2:
5
1 2 3 4 5
输出样例2:
4
输入样例3:
5
7 6 4 3 1
输出样例3:
0
样例解释
样例1:在第 2 天(股票价格 = 1)的时候买入,在第 3 天(股票价格 = 5)的时候卖出, 这笔交易所能获得利润 = 5-1 = 4 。随后,在第 4 天(股票价格 = 3)的时候买入,在第 5 天(股票价格 = 6)的时候卖出, 这笔交易所能获得利润 = 6-3 = 3 。共得利润 4+3 = 7。

样例2:在第 1 天(股票价格 = 1)的时候买入,在第 5 天 (股票价格 = 5)的时候卖出, 这笔交易所能获得利润 = 5-1 = 4 。注意你不能在第 1 天和第 2 天接连购买股票,之后再将它们卖出。因为这样属于同时参与了多笔交易,你必须在再次购买前出售掉之前的股票。

样例3:在这种情况下, 不进行任何交易, 所以最大利润为 0。

思路

有多种购买方案
在这里插入图片描述
遇增就买
一天天看,只要后一天比前一天大,那我就买前一天,后一天卖,一次看相邻的两天
重要性质:
如果交易跨越很多天,一定可以拆分成一段段的,也就是可以拆分成跨度是1天的交易
所以对于任何一种方案,他都等价于一堆跨度是一天的交易的集合,最优解也是等于长度为1的最优解的集合
在这里插入图片描述

证明

对于任何购买方案:
在这里插入图片描述
价值之和最大值等于:集合内长度唯一,差值为增的和

代码

贪心策略
import java.util.*;

public class Main {
    static final int N = 100010;
    static int n;
    static int[] a = new int[N];
    
    public static void main(String[] args) {
        Scanner in = new Scanner (System.in);
        int sum = 0;
        n = in.nextInt();
        for (int i = 0; i < n; i ++ ) a[i] = in.nextInt();
        
        for (int i = 1; i < n; i ++ ) {
            if (a[i - 1] < a[i]) sum += a[i] - a[i - 1];
        }
        
        System.out.print(sum);
    }
}
dp

AcWing 104. 货仓选址

在一条数轴上有 N 家商店,它们的坐标分别为 A1∼AN。

现在需要在数轴上建立一家货仓,每天清晨,从货仓到每家商店都要运送一车商品。

为了提高效率,求把货仓建在何处,可以使得货仓到每家商店的距离之和最小。

输入格式
第一行输入整数 N。

第二行 N 个整数 A1∼AN。

输出格式
输出一个整数,表示距离之和的最小值。

数据范围
1≤N≤100000,
0≤Ai≤40000
输入样例:
4
6 2 9 1
输出样例:
12

思路

目标:仓库到所有商店的距离之和最小
在这里插入图片描述
如上图,最小就是12
猜想:有奇数个商店,把仓库建立在中位数点上;有偶数个商店的话,把仓库建立在中间两个之间。
那如何证明上面的猜想是否正确?
推公式:
先建立一个数学模型:
设每个商店的坐标为xi;
在这里插入图片描述
设最终的仓库的坐标为C
商店Ai与仓库的距离为 |xi - c| 目标 ,距离和最小
在这里插入图片描述
在这里插入图片描述

分组的技巧:

如果商店数量为奇数:
在这里插入图片描述
求每一个组合的最小值
C到两个点的距离之和最小
在这里插入图片描述只有当C在Ai、Aj之间,才可以取到最小值
所以对于每个组,只有当C在每个组的范围内,才可以取到最小值,特别是当商店数为奇数时,C会在最中间的商店上。

在这里插入图片描述
C在中间(奇数为中点,偶数为中间两点之间),则每一组均取最小值,此时整个等式取最小值
先读入商店,然后排序,让仓库取到中点,然后求每个商店与仓库的距离,然后累加起来。

代码

import java.io.*;
import java.util.*;

public class Main {
    static final int N = 100010;
    static int n;
    static int[] a = new int[N];
    
    static int Int(String s) {
        return Integer.parseInt(s);
    }
    
    public static void main(String[] args) throws IOException {
        BufferedReader in =  new BufferedReader(new InputStreamReader(System.in));
        n = Int(in.readLine());
        String[] s = in.readLine().split(" ");
        for (int i = 0; i < n; i ++ ) a[i] = Int(s[i]);
        
        Arrays.sort(a, 0, n);
        int c = a[n / 2];
        int res = 0;
        for (int i = 0; i < n; i ++ ) res += Math.abs(a[i] - c);
        
        System.out.print(res);
    }
}

AcWing 122. 糖果传递

有 n 个小朋友坐成一圈,每人有 a[i] 个糖果。

每人只能给左右两人传递糖果。

每人每次传递一个糖果代价为 1。

求使所有人获得均等糖果的最小代价。

输入格式
第一行输入一个正整数 n,表示小朋友的个数。

接下来 n 行,每行一个整数 a[i],表示第 i 个小朋友初始得到的糖果的颗数。

输出格式
输出一个整数,表示最小代价。

数据范围
1≤n≤1000000,
0≤a[i]≤2×109,
数据保证一定有解。

输入样例:
4
1
2
5
4
输出样例:
4

思路

题目要求我们求出每个小朋友手里糖果数量相同时,最小的代价是多少
先求出n个小朋友手里糖的总数,然后求平均值,得出每个小朋友的平均糖果数
例如下图右边,求出每个小朋友糖果平均数为3,然后根据这个平均值来分配糖果,具体看右图
最后可以得出代价为,1 + 1 + 2 = 4
在这里插入图片描述
数据范围大,只能转换为时间复杂度为O(n)或者O(nlogn)
这道题可以转换成上一道题
如何转换?
先画出数学模型来分析一下:
在这里插入图片描述目标:最小化|x1| + |x2| + … +|xi|
限制:
在这里插入图片描述
接下来求解方程组:
问题1:方程有唯一的解吗?
并不是,题目也说明了有多种方案,而且该方程组是一个恒等式,左边式子全部相加xi可以全部约掉,这说明了有一个方程是多余的,其实应该是n - 1 个独立的方程和n 个未知数

在这里插入图片描述
最终的解,可以用某一个xi 表示其他所以的xi
(这是一个线性代数的知识)
在这里插入图片描述
具体求解:
在这里插入图片描述

这样我们就可以发现,可以用x1来表示每一个xi的
最后带入到上面的最小化式子里:
后面a哪一部分,可以看做是常数Ci
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
Ci可以用递推的方式求出来,时间复杂度是O(n);
求完Ci之后,然后排个数,然后求出每个Ci 到 中点xi的距离距离之和
因为需要排序,排序是本题的瓶颈,所以时间复杂度为O(nlogn)

代码

import java.io.*;
import java.util.*;

public class Main {
    static final int N = 1000010;
    static int n;
    static int[] a = new int[N];
    static long[] c = new long[N];
    
    static int Int(String s) {
        return Integer.parseInt(s);
    }
    
    public static void main(String[] args) throws IOException {
        BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
        n = Int(in.readLine());
        long sum = 0;
        for (int i = 1; i <= n; i ++ ) {
            a[i] = Int(in.readLine());
            sum += a[i];
        }
        
        long avg = sum / n;
        for (int i = n; i > 1; i -- ) {
            c[i] = c[i + 1] + avg - a[i];
        }
        c[1] = 0;
        
        Arrays.sort(c, 1, n + 1);
        
        long res = 0;
        for (int i = 1; i <= n; i ++ ) res += Math.abs(c[i] - c[(i + 1) / 2]);
        System.out.print(res);
    }
}

AcWing 112. 雷达设备

假设海岸是一条无限长的直线,陆地位于海岸的一侧,海洋位于另外一侧。

每个小岛都位于海洋一侧的某个点上。

雷达装置均位于海岸线上,且雷达的监测范围为 d,当小岛与某雷达的距离不超过 d 时,该小岛可以被雷达覆盖。

我们使用笛卡尔坐标系,定义海岸线为 x 轴,海的一侧在 x 轴上方,陆地一侧在 x 轴下方。

现在给出每个小岛的具体坐标以及雷达的检测范围,请你求出能够使所有小岛都被雷达覆盖所需的最小雷达数目。

输入格式
第一行输入两个整数 n 和 d,分别代表小岛数目和雷达检测范围。

接下来 n 行,每行输入两个整数,分别代表小岛的 x,y 轴坐标。

同一行数据之间用空格隔开。

输出格式
输出一个整数,代表所需的最小雷达数目,若没有解决方案则所需数目输出 −1。

数据范围
1≤n≤1000
输入样例:
3 2
1 2
-3 1
2 1
输出样例:
2

思路

先模拟一遍样例

在这里插入图片描述
数据范围小,时间复杂度不超过n2longn就好
雷达只能在线上建立,我们只能在一个线上考虑雷达的位置

先建立好雷达,然后再遍历每个点是否被雷达覆盖,这样比较慢,所以我们需要转变一下思维
不考虑哪些小岛被雷达覆盖,而是反过来考虑雷达要建立在哪些位置上才可以覆盖到小岛
例如,如果雷达的覆盖半径为2,而有个小岛的y轴距离也为2,那么在x = 2 的地方必须要建立一个雷达
在这里插入图片描述
对于上面的这个点,雷达要覆盖它的话,必须在[2 - 根号3 , 2 + 根号3]范围内
在这里插入图片描述
问题转换为:
给定若干区间,问最少要选多少个点(雷达)的位置,可使每个区间上最少选一个点
区间贪心
贪心策略:
1、先将所有区间按右端点排序
2、扫描每个线段,
情况1:如果上一个点不在区间中,则选右端点(贪心体现,如果说,上一个点不在该区间,我们选右端点,更可能覆盖到后面的点)
情况2:如果上一个点在区间中,说明被覆盖,则跳过
例如:
在这里插入图片描述
参数设置:
cnt:算法得到的结果
opt:最优解
需要证明:
1、opt <= cnt
2、opt >= cnt
结合以上两点,得出opt = cnt
在这里插入图片描述
下一个点的选取,取决于上一个点是否覆盖下一个点的区间,看上图,第一个红点不在第二个红点的区间内,在第二个红点区间的左端点的左边,所以可以说,存在有cnt个互不相交的区间,且至少需要选一个点,所以对于所有解>= cnt

代码

import java.util.*;
import java.io.*;

public class Main {
    static final int N = 1010;
    static int n, d;
    static PIIs[] p = new PIIs[N];
    static int Int(String s) {
        return Integer.parseInt(s);
    }
    
    public static void main(String[] args) throws IOException{
        BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
        String[] s = in.readLine().split(" ");
        n = Int(s[0]);
        d = Int(s[1]);
        boolean failed = false;
        for (int i = 0; i < n; i ++ ) {
            s = in.readLine().split(" ");
            int x = Int(s[0]);
            int y = Int(s[1]);
            if (y > d) failed = true;
            else {
                double len = Math.sqrt(d * d - y * y);
                p[i] = new PIIs(x - len, x + len);
            }
        }
        
        if (failed) System.out.printf("-1\n");
        else {
            Arrays.sort(p, 0, n);
            int cnt = 0;
            double last = Integer.MIN_VALUE;
            for (int i = 0; i < n; i ++ ) {
                if (last < p[i].l){
                    cnt ++;
                    last = p[i].r;
                }
            }
            System.out.print(cnt);
        }
        
    }
    
}

class PIIs implements Comparable<PIIs>{
    double l, r;
    public PIIs(double l, double r) {
        this.l = l;
        this.r = r;
    }
    
    public int compareTo(PIIs o) {
        return Double.compare(r, o.r);
    }
}

AcWing 1235. 付账问题

几个人一起出去吃饭是常有的事。

但在结帐的时候,常常会出现一些争执。

现在有 n 个人出去吃饭,他们总共消费了 S 元。

其中第 i 个人带了 ai 元。

幸运的是,所有人带的钱的总数是足够付账的,但现在问题来了:每个人分别要出多少钱呢?

为了公平起见,我们希望在总付钱量恰好为 S 的前提下,最后每个人付的钱的标准差最小。

这里我们约定,每个人支付的钱数可以是任意非负实数,即可以不是 1 分钱的整数倍。

你需要输出最小的标准差是多少。

标准差的介绍:标准差是多个数与它们平均数差值的平方平均数,一般用于刻画这些数之间的“偏差有多大”。

形式化地说,设第 i 个人付的钱为 bi 元,那么标准差为 :

在这里插入图片描述

输入格式
第一行包含两个整数 n、S;

第二行包含 n 个非负整数 a1, …, an。

输出格式
输出最小的标准差,四舍五入保留 4 位小数。

数据范围
1≤n≤5×105,
0≤ai,S≤109
输入样例1:
5 2333
666 666 666 666 666
输出样例1:
0.0000
输入样例2:
10 30
2 1 4 7 4 8 3 6 4 7
输出样例2:
0.7928

思路

猜想:
如果每个人的钱带够,那么每个人付平均值块钱就可以了,
如果有一些人没带够,那么不够平均值快钱的人要把所有的钱拿出来,不够的一部分分摊给其他人,如果其他人中的某个人因为不够钱分摊的话,其分摊的部分由剩下几位分摊,以此往复,便可求出最小标准差,
证明:
涉及到均值不等式:
在这里插入图片描述
分析
在这里插入图片描述
ai为每个人实际带的钱,bi为应该付的钱,当ai全都大于平均值时,全部付平均值的钱
如果有一部分ai钱不够平均值怎么办:把所有的钱拿出来付款,不足的部分交给其他超过平均值的ai分担,假设有一个aj>平均值, 它分担ai不足的部分钱,我们先来单独分析这两个数,把他们两个看做是只有两项的均值不等式:
在这里插入图片描述
a + b = C 是固定的,然后构造一个(a - b)
在这里插入图片描述
由上面的式子得知,当两个数的和相同的情况下,两个数的差越小,两个数平方和越小
所以:在这里插入图片描述
对于上面的式子,如果bi取小的话,bj就得取大,平方和反而增大,所以bi必须取ai的全部钱

解题策略:
先把ai从小到大排序
如果:
a1 < s / n b1 = ai

整个方案,由a1来决定, 也就是后面 ai > s / n的人的值取固定的,
在这里插入图片描述

如果:
a2 < s / n
解决方案也如同上面a1一样

代码

import java.util.Arrays;
import java.util.Scanner;

public class Main {
    static final int N = 500010;
    static int[] a = new int[N];
    public static void main(String[] args) {
        Scanner in = new Scanner (System.in);
        int n = in.nextInt();
        double s = in.nextDouble();
        for (int i = 0; i < n; i ++ ) {
            a[i] = in.nextInt();
        }
        Arrays.sort(a, 0, n);
        double res = 0, avg = s / n;
        for (int i = 0; i < n; i ++ ) {
            double cur = s / (n - i);
            if (a[i] < cur) cur = a[i];
            res += (cur - avg) * (cur - avg);
            s -= cur;
        }
        
        System.out.printf("%.4f", Math.sqrt(res / n));
    }
}

AcWing 1239. 乘积最大

给定 N 个整数 A1,A2,…AN。

请你从中选出 K 个数,使其乘积最大。

请你求出最大的乘积,由于乘积可能超出整型范围,你只需输出乘积除以 1000000009 的余数。

注意,如果 X<0, 我们定义 X 除以 1000000009 的余数是负(−X)除以 1000000009 的余数,即:0−((0−x)%1000000009)
输入格式
第一行包含两个整数 N 和 K。

以下 N 行每行一个整数 Ai。

输出格式
输出一个整数,表示答案。

数据范围
1≤K≤N≤105,
−105≤Ai≤105
输入样例1:
5 3
-100000
-10000
2
100000
10000
输出样例1:
999100009
输入样例2:
5 3
-100000
-100000
-2
-100000
-100000
输出样例2:
-999999829

思路

本质上是一个分类算法,
首先我们知道 如果 k == n ,那么就证明所有的数字是全部都选,
如果 k < n , 那么就要思考怎样去选择了:
1.k 如果是偶数的话,选出来的结果一定是非负数 , 原因如下:
(1) # 负数的个数是偶数个的话,负负得正,那么一定是非负数
(2) # 负数的个数如果是奇数个的话,那么我们就只选偶数个绝对值最大的负数
2.k 如果是奇数个的话,
(1)# 所有的数字如果都是负数,那么选出来的结果也一定都是负数
(2)# 否则的话,则一定至少有 1个非负数, 那么我们将最大的数取出来, 此时要选的个数就是 k–,
# k-- 是偶数,那么就又转化为 k-- 是偶数的情况思考

代码

import java.io.*;
import java.util.*;

public class Main {
    static final int N = 100010;
    static int[] a = new int[N];
    
    public static void main(String[] args) throws IOException {
        BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
        String[] s = in.readLine().split(" ");
        int n = Integer.parseInt(s[0]);
        int k = Integer.parseInt(s[1]);
        for (int i = 1; i <= n; i ++ ) {
            a[i] = Integer.parseInt(in.readLine());
        }
        Arrays.sort(a, 1, n + 1);
        long som = 1;
        for (int i = n, j = 0; j < k; i --, j ++ ) {
            som *= a[i];
            System.out.println(a[i]);
            
        }
        System.out.println(som);
        if (som > 0) som %= 1000000009;
        else som %= 0 - ((0 - som) % 1000000009);
        System.out.println(som);
        System.out.println((-1 * -1));
    }
}

AcWing 1247. 后缀表达式(这道题建议多看几遍)

给定 N 个加号、M 个减号以及 N+M+1 个整数 A1,A2,⋅⋅⋅,AN+M+1,小明想知道在所有由这 N 个加号、M 个减号以及 N+M+1 个整数凑出的合法的后缀表达式中,结果最大的是哪一个?

请你输出这个最大的结果。

例如使用 123+−,则 “23+1−” 这个后缀表达式结果是 4,是最大的。

输入格式
第一行包含两个整数 N 和 M。

第二行包含 N+M+1 个整数 A1,A2,⋅⋅⋅,AN+M+1。

输出格式
输出一个整数,代表答案。

数据范围
0≤N,M≤105,
−109≤Ai≤109
输入样例:
1 1
1 2 3
输出样例:
4

思路

后缀表达式:也称为逆波兰式
45 + 32- - 转换成人类思维模式是: 1、4 + 5 = 9 2、3 - 2 = 1 3、 9 - 1 = 8
所以最终答案为8;
这是机器最喜欢的表达式,这个表达式是类似栈的形式,计算机里面和栈相关的基本也与树相关
可以转换成一课二叉树,
在这里插入图片描述
这个树的后序遍历就是这个逆波兰表达式
非叶子节点都是符号
叶子节点都是数
在这里插入图片描述
这道题需要注意的是,符号的抵消,负负得正
在这里插入图片描述
例如上面的式子我们可以转换成 b - (a0 - a1 - a2 - a3 - a4) 把里面都转为相加
树的形状为:
在这里插入图片描述
而且表达式的负号数量是可以改变的,例如把a4从括号里单独拿出来,负号就会多一个
也就是负号的数量可以为1 ~ m个
在来考虑一下比较复杂的情况
如果给我们m 个负号,n个正号,
在这里插入图片描述
把加号放到括号里面,可以增加负号的个数
可以增加的负号的个数为1 ~ m + n
因为负号最少有一个,那么我们就有,至少减一个最小值,加一个最大值
除了最大值和最小值外,其余所有数,如果本身是正号,我们就给它正号,本来是负号,我们就给它负号
在这里插入图片描述

代码

import java.util.*;

public class Main {
    static final int N = 200010;
    static int n, m;
    static int[] a = new int[N];
    
    public static void main(String[] args) {
        Scanner in = new Scanner(System.in);
        n = in.nextInt();
        m = in.nextInt();
        int k = m + n + 1;
        for (int i = 0; i < k; i ++ ) a[i] = in.nextInt();
        Arrays.sort(a, 0, k);
        long res = 0;
        if (m == 0){
            for (int i = 0; i < k; i ++ ) res += a[i];   
        }
        else {
            res = a[k - 1] - a[0];
            for (int i = 1; i < k - 1; i ++ ) res += Math.abs(a[i]);
        }
        System.out.print(res);
    }
}

AcWing 1248. 灵能传输(多学一下思路)

在游戏《星际争霸 II》中,高阶圣堂武士作为星灵的重要 AOE 单位,在游戏的中后期发挥着重要的作用,其技能”灵能风暴“可以消耗大量的灵能对一片区域内的敌军造成毁灭性的伤害。

经常用于对抗人类的生化部队和虫族的刺蛇飞龙等低血量单位。

你控制着 n 名高阶圣堂武士,方便起见标为 1,2,⋅⋅⋅,n。

每名高阶圣堂武士需要一定的灵能来战斗,每个人有一个灵能值 ai 表示其拥有的灵能的多少(ai 非负表示这名高阶圣堂武士比在最佳状态下多余了 ai 点灵能,ai 为负则表示这名高阶圣堂武士还需要 −ai 点灵能才能到达最佳战斗状态)。

现在系统赋予了你的高阶圣堂武士一个能力,传递灵能,每次你可以选择一个 i∈[2,n−1],若 ai≥0 则其两旁的高阶圣堂武士,也就是 i−1、i+1 这两名高阶圣堂武士会从 i 这名高阶圣堂武士这里各抽取 ai 点灵能;若 ai<0 则其两旁的高阶圣堂武士,也就是 i−1,i+1 这两名高阶圣堂武士会给 i 这名高阶圣堂武士 −ai 点灵能。

形式化来讲就是 ai−1+=ai,ai+1+=ai,ai−=2ai。

灵能是非常高效的作战工具,同时也非常危险且不稳定,一位高阶圣堂武士拥有的灵能过多或者过少都不好,定义一组高阶圣堂武士的不稳定度为 maxni=1|ai|,请你通过不限次数的传递灵能操作使得你控制的这一组高阶圣堂武士的不稳定度最小。

输入格式
本题包含多组询问。输入的第一行包含一个正整数 T 表示询问组数。

接下来依次输入每一组询问。

每组询问的第一行包含一个正整数 n,表示高阶圣堂武士的数量。

接下来一行包含 n 个数 a1,a2,⋅⋅⋅,an。

输出格式
输出 T 行。

每行一个整数依次表示每组询问的答案。

数据范围
1≤T≤3,3≤n≤300000,|ai|≤109,
每个评测用例的限制如下:

在这里插入图片描述

输入样例1:
3
3
5 -2 3
4
0 0 0 0
3
1 2 3
输出样例1:
3
0
3
输入样例2:
3
4
-1 -2 -3 7
4
2 3 4 -8
5
-1 -1 6 -1 -1
输出样例2:
5
7
4
样例解释
样例一
对于第一组询问:
对 2 号高阶圣堂武士进行传输操作后 a1=3,a2=2,a3=1。答案为 3。
对于第二组询问:
这一组高阶圣堂武士拥有的灵能都正好可以让他们达到最佳战斗状态。

思路

样例模拟
在这里插入图片描述
难点1:用前缀和
在这里插入图片描述
我们可以发现一个惊人的规律,对于前缀和,这三个操作不会对前缀和产生影响(加的ai等于减去的ai),ai-1 加上一个ai的话就变成了si,
而ai 减去了2个ai,由于后面的ai+1 多加了1个ai(就相当于是si+1 + ai),抵消了一个ai,所以实际上,ai减去了一个ai,就变成了si-1,也就是说,每次的灵能传输操作转化为了前缀和的交换操作,结合题目范围,1,n是不能交换的,也就是前缀和里s0, s1和 sn -1 , sn是不能交换的,
在这里插入图片描述
例如:5 -2 3
前缀和为:0, 5, 3, 6
交换后:0, 3, 5, 6
除了0, 6不能交换
然后求每个ai的最小值可以转换为:求让每个前缀和之间的差值的最小里的最大值的最小值
把s的值压缩到y轴上

在这里插入图片描述
重复走的路线可以跳着走,(应为前缀和每个点只能访问一次)这样走就可以保证是最优了,
为什么呢?
用反正法证明:
如果跳着走不是最优解会是什么情况
假设隔一个跳的时候,最优解是红色线段,如果这个红色线段不是最优解的话,那么第二个点只能直接走到第三个点,比红色线段要段,但是,跳回来的时候,一定严格大于红色线段,这与最优解不是隔一个跳是矛盾的,所以最优解就是隔一个跳着走。
在这里插入图片描述

代码

import java.io.*;
import java.util.*;

public class Main {
    static final int N = 300010;
    static long[] a = new long[N];
    static long[] s = new long[N];
    static boolean[] st = new boolean[N];
    
    static int Int(String s) {
        return Integer.parseInt(s);
    }
    
    static long LL(String s) {
        return Long.parseLong(s);
    }

    
    public static void main(String[] args) throws IOException {
        BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
        int T = Int(in.readLine());
        while (T -- > 0) {
            int n = Int(in.readLine());
            String[] ss = in.readLine().split(" ");
            for (int i = 1; i <= n; i ++ ) {
                s[i] = s[i - 1] + LL(ss[i - 1]);
            }
            long s0 = s[0], sn = s[n];
            if (s0 > sn) {
                long temp = s0;
                s0 = sn;
                sn = temp;
            } // 因为是对称的,所以可以交换
            Arrays.sort(s, 0, n + 1); //要全排序
            
            for (int i = 0; i <= n; i ++ ) {
                if (s[i] == s0) {
                    s0 = i; // 找到第一个与s0相同的前缀和并把记下下标
                    break;
                }
            }
            for (int i = n; i >= 0; i -- ) {
                if (s[i] == sn) {
                    sn = i;
                    break;
                }
            }
            Arrays.fill(st, false);
            int l = 0, r = n;
            for (int i = (int)s0; i >= 0; i -= 2) {
                a[l ++ ] = s[i];
                st[i] = true;
            }
            for (int i = (int)sn; i <= n; i += 2) {
                a[r -- ] = s[i];
                st[i] = true;
            }
            for (int i = 0; i <= n; i ++ ) {
                if (!st[i])
                    a[l ++ ] = s[i];
            }
            long res = 0;
            for (int i = 1; i <= n; i ++ ) res = Math.max(res, Math.abs(a[i] - a[i - 1]));
            System.out.println(res);
        }
    }
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值