【题目】灵能传输(贪婪算法,蓝桥杯)
这次我们来看一道题目,是来自第十届蓝桥杯C/C++和Java B组省赛最后一题。
由于题目有点长,这里只放出题目部分内容。
如图,位于题目结尾处提示了我们该题目输入量比较大,需要快速读入的方式,如果是Java选手,我们最好写一个快速输入类,避免超时。
根据本题数据的范围,可以需要快速输入类里面写一个long型和int型的数据的输入方法即可。
代码如下:
class JavaIn {
static private BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
static private StringTokenizer sti = null;
//long数组快速输入
public static void in(long[] arr) {
int len = arr.length;
//这里特殊处理一下,从1开始输入
for (int i = 1; i < len; i++) {
arr[i] = nextLong();
}
}
public static String next() {
while (sti == null || !sti.hasMoreTokens()) {
try {
sti = new StringTokenizer(br.readLine());
} catch (IOException e) {
e.printStackTrace();
}
}
return sti.nextToken();
}
public static long nextLong() {
return Long.parseLong(next());
}
public static int nextInt(){
return Integer.parseInt(next());
}
}
利用这个方法进行输入输出比Scanner类的效率要高,但缺点是只能读入字母或者数字这样的数据,当然对于本题是适用的,但对于其他题目需要注意一下输入的数据类型。
下面开始分析本题,题目比较长,但是需要抓住题目的核心内容是:“形式化来讲就是 ai−1+=ai,ai+1+=ai,ai−=2ai”以及“不稳定度最小”这两句话,比如第一个测试用例的第一个,“5,-2,3”,(ai-1 = 5,ai+1 = 3,ai = -2),根据题目的这个操作后,变成了:
a[i-1] = 5+(-2) = 3
a[i+1] = 3 +(-2) = 1
a[i] = -2-(-2)*2 = 2
这就是第一个样例的解释内容,这组案例比较特殊,刚好只有3个高阶圣堂武士,如果更多呢,那么我们就不太好处理了。
这就需要一些前缀和的一些知识。
前缀和也就是一个数组记录某个下标之前另一个数组的所有元素之和,也就是假设数组 a = [0,1,2];
它的前缀和数组b满足, b[0] = a[0] = 0, b[1] = a[0] + a[1] = 2 …
当然这只是一维前缀和。对于此题我们只了解一维的即可。
回到这道题,利用到我们刚才所了解到的前缀和知识,我们设数组a也就是题目中高阶圣堂武士的灵能的前缀和为s,则s[i-1] = a[0] + … + a[i-1]; s[i] = a[0] + … + a[i] ; s[i+1] = a[0] + … + a[i+1]。
特殊的s[0] = a[0]
不难发现第一个测试用例已经提示了我们,如果对所有的a进行排序,我们要使得这些所有的前缀和的相邻两个之差的绝对值中最大值最小即可。
以下是完整代码实现(一些细节的话在注释里面):
import java.io.*;
import java.util.Arrays;
import java.util.StringTokenizer;
public class T10_灵能传输 {
public static void main(String[] args) {
int T;
T = JavaIn.nextInt();
while (T-- != 0) {
int n = JavaIn.nextInt();
long[] a = new long[n + 1];
//前缀和
long[] s = new long[n + 1];
long ans = 0;
JavaIn.in(a);
s[0] = 0;
for (int i = 1; i <= n; i++) {
s[i] = s[i - 1] + a[i];
}
long s_0 = s[0];
long s_n = s[n];
if (s_0 > s_n) {
long t = s_0;
s_0 = s_n;
s_n = t;
}
Arrays.sort(s);
for (int i = 0; i <= n; i++) {
if (s_0 == s[i]) {
s_0 = i;
break;
}
}
for (int i = 0; i <= n; i++) {
if (s_n == s[i]) {
s_n = i;
break;
}
}
//建立标记数组,避免处理奇偶问题
boolean[] marks = new boolean[n + 1];
int leftPtr = 0;
int rightPtr = n;
//储存传递完的灵能
long[] ss = new long[n + 1];
//跳着取
for (int i = (int) s_0; i >= 0; i -= 2) {
ss[leftPtr++] = s[i];
//标记
marks[i] = true;
}
for (int i = (int) s_n; i <= n; i += 2) {
ss[rightPtr--] = s[i];
//标记
marks[i] = true;
}
for (int i = 0; i <= n; i++) {
//没被标记的就存储起来
if (!marks[i]) {
ss[leftPtr++] = s[i];
}
}
for (int i = 1; i <= n; i++) {
ans = Math.max(ans, Math.abs(ss[i] - ss[i - 1]));
}
System.out.println(ans);
}
}
}
class JavaIn {
static private BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
static private StringTokenizer sti = null;
//long数组快速输入
public static void in(long[] arr) {
int len = arr.length;
//这里特殊处理一下,从1开始输入
for (int i = 1; i < len; i++) {
arr[i] = nextLong();
}
}
public static String next() {
while (sti == null || !sti.hasMoreTokens()) {
try {
sti = new StringTokenizer(br.readLine());
} catch (IOException e) {
e.printStackTrace();
}
}
return sti.nextToken();
}
public static long nextLong() {
return Long.parseLong(next());
}
public static int nextInt() {
return Integer.parseInt(next());
}
}
提交后的结果:
总结,这道题目的一些策略的确不好想,我比较菜,也是参考了大佬的思路才能写出来。