第十届蓝桥杯软件类省赛 Java 大学 B 组 题目以及详细解析


包含全部题目和解析以及AC代码,并确保代码正确性。

给参加蓝桥杯的小伙伴们推荐个OJ: New Online Judge
这个OJ的题库里有蓝桥杯历年的省赛和决赛真题,能亲眼看到AC才能确保代码正确性。

结果填空题

A

在这里插入图片描述在这里插入图片描述
答案:
97 + 99 + 99 + 97 + 98 = 490 选法不唯一, 和唯一。

找出每个位置评分最高的,并且每个人只能担任一个号位。

因为数据很少,直接手算就可以,但是如果这道题是编程题,数据量很大,好像不太好处理。

B

在这里插入图片描述
按子串长度从(1~n)对原字符串进行截取,并判断该字符串是否出现过,没有就把答案加一。

做法是可以创建一个set集合,将所有子串加入进去,最后集合中字符串的数量就是答案。

代码:

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

public class Main{
    public static void main(String[] args){
        Scanner in = new Scanner(System.in);
        
        HashSet<String> res = new HashSet<String>();
        String s = in.nextLine();
        int n = s.length();
        for(int i = 0; i < n; i++){
            for(int j = i+1; j <= n; j++){
                res.add(s.substring(i, j));
            }
        }
        
        System.out.println(res.size());
    }
}

答案:100

C

在这里插入图片描述
这道题就是由斐波那契数列变形而来的,但是题目要的是第20190324项的后四位数字,如果真的求出第20190324项,在取出四位,用大数类也算不出来,因为数太大了。
然而仔细一想,其实我们计算的只需要保留每一项的后四位就行了,因为其他位影响不到最终答案。 因为每一项都由前三项加和得来,所以计算过程中只需要用四个变量保留前三项和答案。

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

public class Main{
    public static void main(String[] args){
        int first = 1, second = 1, third = 1;
        int res = 0;
        for(int i = 4; i <= 20190324; i++){
            res = (first + second + third)%10000;
            first = second;
            second = third;
            third = res;
        }
        System.out.println(res);
    }
}

答案:4659

D

在这里插入图片描述
从小到大枚举三个正整数i,j,l,并且 a <= b <= c,这是为了防止计算重复。
在枚举的过程中判断每个数是不是包含2和4. 最后判断a+b+c是不是等于2019.

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

public class Main{
   public static Boolean find(int x){
	   while(x != 0){
		  if(x%10 == 2 || x%10 == 4){
			  return true;
		  }
		  x /= 10;
	   }
	   return false;
   }
	
	public static void main(String[] args) throws IOException{
		File file = new File("E:\\output.txt");
		
		if(file.exists())
			file.createNewFile();				//如果指定文件不存在,新建文件
				
			BufferedWriter out = new BufferedWriter(new FileWriter(file));
	    	long sum = 0;
	        for(int i = 1; i <= 2019; i++)
	        {
	        	if(find(i))	continue;
	        		
	            for(int j = i+1; j + i <= 2019; j++)
	            {
	            	if(find(j)) continue;
	                
	            	for(int k = j+1; k + j <= 2019; k++)
	                {
	            		if(find(k)) continue;
	            		
	            		if(i + j + k == 2019){
	                        sum++;
	                        out.write(i + " " + j + "  " + k+ "\n");
	                    }
	                }
	            }
	        }
	        
	        out.close();
	        System.out.print(sum);
	}
}


为了方便查看答案的正确性, 将所有组合输出到output文件中。
答案 :40785

E

在这里插入图片描述
迷宫:


01010101001011001001010110010110100100001000101010
00001000100000101010010000100000001001100110100101
01111011010010001000001101001011100011000000010000
01000000001010100011010000101000001010101011001011
00011111000000101000010010100010100000101100000000
11001000110101000010101100011010011010101011110111
00011011010101001001001010000001000101001110000000
10100000101000100110101010111110011000010000111010
00111000001010100001100010000001000101001100001001
11000110100001110010001001010101010101010001101000
00010000100100000101001010101110100010101010000101
11100100101001001000010000010101010100100100010100
00000010000000101011001111010001100000101010100011
10101010011100001000011000010110011110110100001000
10101010100001101010100101000010100000111011101001
10000000101100010000101100101101001011100000000100
10101001000000010100100001000100000100011110101001
00101001010101101001010100011010101101110000110101
11001010000100001100000010100101000001000111000010
00001000110000110101101000000100101001001000011101
10100101000101000000001110110010110101101010100001
00101000010000110101010000100010001001000100010101
10100001000110010001000010101001010101011111010010
00000100101000000110010100101001000001000000000010
11010000001001110111001001000011101001011011101000
00000110100010001000100000001000011101000000110011
10101000101000100010001111100010101001010000001000
10000010100101001010110000000100101010001011101000
00111100001000010000000110111000000001000000001011
10000001100111010111010001000110111010101101111000


思路:
这道题就是BFS求最短路径,然后题目要求的是最短路径的走法,DULR分别表示下上左右,并且在步数相等的情况下,要求字典序最小。

  1. 首先解决字典序最小的问题,字典序 D < < < L < < < R < < < U
    所以为了使字典序最小,我们要按照D L R U的先后顺寻来搜索。

  2. 搜索的时候用一个数组记录一下当前点是由哪个点转移过来的。
    最后从出口倒推到入口就可以了。

  3. 因为是地图是二维的,所以要保存上一个点的横坐标和纵坐标,可以写一个pair类,并创建为二维数组,例如:pre[nx][ny].x = i, pre[nx][ny].y = j表示点(nx, ny)由(i, j)走过来。

    或者直接开个三维数组,第三维大小开2,分别表示横坐标和纵坐标。
    例如:pre[nx][ny][0] = i, pre[nx][ny][1] = j表示点(nx, ny)由(i, j)走过来。

代码:

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

public class Main{
    static BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
    static BufferedWriter out = new BufferedWriter(new OutputStreamWriter(System.out));
    
    static final int N = 1005;
    static int n, m;
    static int[][] g = new int[N][N], st = new int[N][N], q = new int[N*N][2];
    static int[][][] pre = new int[N][N][2];
    
    public static int Int(String s){
        return Integer.parseInt(s);
    }
    
    public static void bfs(int i, int j) throws IOException{
        int[] dx = {1, 0, 0, -1}; // 下 左 右 上
        int[] dy = {0, -1, 1, 0};
        
        int h = 0, t = 0;
        q[t][0] = i; q[t++][1] = j;
        
        st[i][j] = 1;
        while(h != t){
            int x = q[h][0], y = q[h++][1];
            
            for(int l = 0; l < 4; l++){
                int nx = x + dx[l];
                int ny = y + dy[l];
                 
                if(nx < 0 || nx >= n || ny < 0 || ny >= m) continue;
                if(g[nx][ny] == 1) continue;             
                if(st[nx][ny] == 1) continue;

                q[t][0] = nx; q[t++][1] = ny;
                st[nx][ny] = 1;
                
                pre[nx][ny][0] = x; pre[nx][ny][1] = y;
                if(nx == n-1 && ny == m-1) return ;
            }
        }
    }
    
    public static void main(String[] args) throws Exception{
        String[] ss = in.readLine().split(" ");
        n = Int(ss[0]);
        m = Int(ss[1]);
        
        for(int i = 0; i < n; i++){
            String s = in.readLine();
            
            for(int j = 0; j < s.length(); j++)
                g[i][j] = Int(String.valueOf(s.charAt(j)));
        }
       
        bfs(0, 0);      
        
        int i = n-1, j = m-1; // 从出口回溯到入口。       
        String res = "";
        
        while(i != 0 || j != 0){
      
            if(i - pre[i][j][0] == 0){ // x不变,说明是向左或者向右。
                if(j - pre[i][j][1] == 1) // 3 2 -> 3 3 说明上一个点是通过向右走到达(i,j)
                    res = "R" + res;
                else
                    res = "L" + res; // 向左
            }
            else{
                if(i - pre[i][j][0] == 1) // 2 3 -> 3 3 说明上一个点是通过向下走到达(i,j)
                    res = "D" + res;
                else
                    res = "U" + res; // 向上
            }
            
            int t = i;
            i = pre[i][j][0];
            j = pre[t][j][1];
        }
        out.write(res);
        out.flush();
    }
}

答案:

DDDDRRURRRRRRDRRRRDDDLDDRDDDDDDDDDDDDRDDRRRURRUURRDDDDRDRRRRRRDRRURRDDDRRRRUURUUUUUUULULLUUUURRRRUULLLUUUULLUUULUURRURRURURRRDDRRRRRDDRRDDLLLDDRRDDRDDLDDDLLDDLLLDLDDDLDDRRRRRRRRRDDDDDDRR

程序设计题

F

在这里插入图片描述
题目链接: https://www.acwing.com/problem/content/description/1247/

题意:求出1~n中包含2, 0, 1, 9这几个的数的数字之和。

数据规模是10000, 所以可以直接枚举1~n中所有的数,判断其是否包含2,0,1,9其中的一个,有就加上。 时间复杂度最高是 O ( 5 ∗ n ) O(5*n) O(5n);完全不用担心超时。

AC代码:

import java.io.*;
import java.util.*;
 
public class Main{     
    public static void main(String[] args) throws Exception{
        
    	Scanner in = new Scanner(System.in);
    	int n = in.nextInt();
         
        int res = 0;
        for(int i = 1; i <= n; i++){
            int x = i;
            while(x != 0){
                int r = x % 10;
                if(r == 0 || r == 1 || r == 2 || r == 9){
                    res += i;
                    break;
                }
                x /= 10;
            }    
        }
         
       System.out.print(res);
    }
}

G

在这里插入图片描述
在这里插入图片描述在这里插入图片描述
题目链接:http://oj.ecustacm.cn/problem.php?id=1458
题意:

有N家店,每家店都有一个优先级,初始时刻都为0,对于任意时刻,如果某家店接到订单,则优先级+2,如果没有则-1,最低为0。 优先级大于5,则加入优先缓存,小于等于3则移除,给出T时刻以内的M条订餐信息,求第T时刻谁在优先缓存中。

思路:

这道题是一个模拟题。我们根据M条信息模拟每家店的优先级加减情况。最后判断一下哪些店位于优先缓存。

数据规模最大是 1 0 5 10^5 105,所以该题的时间复杂度必须小于 o ( n 2 ) o(n^2) o(n2)

如果我们枚举从1到T的每个时刻,然后判断每个时刻有哪些店接到了订单,哪些店没有接到订单,因为T和N都是 1 0 5 10^5 105,所以这样的时间复杂度是 O ( n 2 ) O( n^2) O(n2)会超时,所以我们换种方式枚举。

因为只有M条信息中出现的店才接到了订单,所以我们只需要考虑有订单的店,因为其他的店的优先级一定都是0.

然后我们可以用这M条信息模拟接到订单的店的优先级的加减情况。
第一步:排序
我们可以先对M条信息按时刻从小到大进行排序,如果时刻相同就按店号从小到大排序。时间复杂度为 O ( n ∗ l o g n ) O(n*logn) O(nlogn),满足要求。

得到了时刻和店号都为升序的订单信息后:
第二步:处理优先级

我们从前到后依次处理每条信息时, 记录一下每家店上次是什么时刻接到订单的, 这样我们就可以算出每家店这次接到订单前,有多少个时刻没有订单,然后将优先级减去多少,如果其优先级小于等于3,就标记为fasle,表示其不在优先缓存,如果减成了负数, 就赋值为0.然后加上这次接到订单的2优先级, 加完后判断其优先级是不是大于5,如果是标记为true,表面将其加入优先缓存中。

第三步:计算接到过订单的店,从最后时刻到T时刻的优先级减了多少。

因为处理完M条信息后,可能存在某些店的优先级大于6,但它在T时刻没有订单,所以我们最后额外处理一下M条信息中的所有店到T时刻后的优先级是多少。
直接用T时刻减去每家店的最后接单时刻,就得到了该点有多少个时刻没有订单。然后优先级相应的减去多少。然后判断该店还在不在优先缓存中。
如果在,答案数就加一。

AC代码:

import java.io.*;
import java.util.*;
 
public class Main{
    static BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
    static BufferedWriter out = new BufferedWriter(new OutputStreamWriter(System.out));
     
    static int[][] arr = new int[100005][2];
    static int[] score = new int[100005];
    static int[] st = new int[100005];
    public static int Int(String s){
        return Integer.parseInt(s);
    }
     
    public static void main(String[] args) throws Exception{
        String[] s = in.readLine().split(" ");
        int N = Int(s[0]);
        int M = Int(s[1]);
        int T = Int(s[2]);
         
        for(int i = 0; i < M; i++){
            String[] ss = in.readLine().split(" ");
            arr[i][0] = Int(ss[0]);
            arr[i][1] = Int(ss[1]);
        }
         
        Arrays.sort(arr, 0, M, new Comparator<int[]>(){
            public int compare(int[] a, int[] b){
                if(a[0] == b[0]) return a[1] - b[1];
                return a[0] - b[0];
            }
        });
         
        int[] last = new int[100005];
        TreeSet<Integer> set = new TreeSet<>(); 
        for(int i = 0; i < M && arr[i][0] <= T; i++){// 处理M条信息,记录每家店的最后接单时间。初始为0
         
            int time = arr[i][0]; 
            int id = arr[i][1];
            set.add(id);// 接到过订单的店
              
            if(time - last[id] > 1)
                   score[id] -=  (time - last[id] - 1);
            if(score[id] < 0) score[id] = 0;
            if(score[id] <= 3) st[id] = 0;  // 要先置为false 否则如果先降到了3再加上2
                                            // 虽然大于3,但不大于5
            score[id] += 2;
                 
            if(score[id] > 5) st[id] = 1;
            
            last[id] = time; // 记录某家店的最后接单时刻。
            //out.write(score[id] + "\n");
        }
         
        int num = 0;
        for(int i : set){
            //out.write("s: " + score[i] + " last: "+ last[i] + "  T: "+ T + "\n");
            if(last[i] < T)
                score[i] -= (T - last[i]);
             
            if(score[i] <= 3)
                st[i] = 0;
            if(st[i] == 1) num ++;
        }
         
        out.write(num +"");
        out.flush();
    }
}

H

在这里插入图片描述在这里插入图片描述
题目链接:http://oj.ecustacm.cn/problem.php?id=1473

这道题就是找出给定区间内Alice和Bob的对数。

我们可以先预处理出来所有的Alice和Bob的位置,然后两个for循环,暴力枚举每个Alice的合法范围内有多个Bob, 但是,因为字符串的长度是一百万,所以这个时间复杂度肯定是会超时的。

那么我们如何进行优化?

我们从前到后依次找到的每个Alice和Bob,其在数组中的位置一定是递增的,那么用for循环挨个找不是太**了?《硅谷》中的男主就因为使用for循环暴力查找有序的序列而被大家无情嘲讽。所以为了不被大佬们嘲讽,我们还是用二分吧, 我们可以分别二分出在合法范围内,Alice最左边的Bob的位置 L 和Alice最右边的Bob的位置 R。然后 R - L + 1就是当前的Alice和Bob同时出现的次数,枚举每个Alice就能得到最终答案。

时间复杂度是 O ( n ∗ l o g n ) O(n*logn) O(nlogn) ,稳过不T。

另外,因为处理出来的Alice和Bob的位置都是升序的,我们也可以用双指针算法进行优化,也就是维护一个动态区间,左区间端点是L, 右区间端点是R,区间内是对于当前位置的Alice而言所有合法的Bob。

先找到一个距离当前Alice右边最远的Bob也就是找到R,然后找到距离当前Alice左边最远的Bob,也就是找到L,累加答案后,遍历下一个位置的Alice,重新维护这个区间。

这里其实和二分的思想是类似的,只不过二分是每次都重新找,而双指针是动态的维护区间左右端点。详见代码。

预处理 + 二分

import java.io.*;
import java.util.*;
import java.math.*;
   
public class Main{
    static BufferedReader in = new BufferedReader(new InputStreamReader(System.in),32768);
    static BufferedWriter out = new BufferedWriter(new OutputStreamWriter(System.out));
      
    static final int N = 1000010;
    static String s;
    static int[] A = new int[N], B = new int[N];
    static int n, cnta, cntb;
      
    public static boolean chack(int i){
        if(i < 0 || i >= n)
            return false; 
        char c = s.charAt(i);
        return c >= 'a' && c <= 'Z' || c >= 'A' && c <= 'Z';
    }
       
    public static int findl(int x) throws IOException{ // 大于等于 x 的最小值
        int l = 0, r = cntb - 1;
        while(l < r){
            int mid = l + r>> 1;
             
            if(B[mid] >= x) r = mid;
            else l = mid + 1;
        }
        return l;
    }
     
    public static int findr(int x) throws IOException{
        int l = 0, r = cntb - 1;
        while(l < r){
            int mid = l + r + 1 >> 1;
            if(B[mid] <= x) l = mid;
            else r = mid - 1;
        }
        return l;
    }
     
    public static void main(String[] args) throws Exception{
        int k = Integer.parseInt(in.readLine());
        s = in.readLine();
         
        n = s.length();
        for(int i = 0; i < n; i++){
            if(!chack(i-1)){
                if(i + 4 < n && s.substring(i, i + 5).equals("Alice") && !chack(i+5))
                    A[cnta ++] = i;  
                else if(i + 2 < n && s.substring(i, i + 3).equals("Bob") && !chack(i+3))
                    B[cntb ++] = i;
            }
        }
          
        long res = 0;
        for(int i = 0; i < cnta; i++){
            int l = findl(A[i] - k - 3); // 找到在合法范围内,Alice最左边的Bob
             
            if(cntb == 0 || B[l] > A[i] + k + 5){ //没找到。
                continue;
            } 
             
            int r = findr(A[i] + k + 5); // 找到合法范围内,Alice最右边的Bob // 1  7 
             
            res += r - l + 1;
        }
        out.write(res + "");
        out.flush();
    }
}

预处理 + 双指针:

import java.io.*;
import java.util.*;
import java.math.*;
  
public class Main{
    static BufferedReader in = new BufferedReader(new InputStreamReader(System.in),32768);
    static BufferedWriter out = new BufferedWriter(new OutputStreamWriter(System.out));
     
    static final int N = 1000010;
    static String s;
    static int[] A = new int[N], B = new int[N];
    static int n;
     
    public static boolean chack(int i){
        if(i < 0 || i >= n)
            return false; 
        char c = s.charAt(i);
        return c >= 'a' && c <= 'Z' || c >= 'A' && c <= 'Z';
    }
      
    public static void main(String[] args) throws Exception{
        int k = Integer.parseInt(in.readLine());
        s = in.readLine();
        
        n = s.length();
        int cnta = 0, cntb = 0;
        for(int i = 0; i < n; i++){
            if(!chack(i-1)){ // 注意不要用 == 判断是否相等。
                if(i + 4 < n && s.substring(i, i + 5).equals("Alice") && !chack(i+5))
                    A[cnta ++] = i;  
                else if(i + 2 < n && s.substring(i, i + 3).equals("Bob") && !chack(i+3))
                    B[cntb ++] = i;
            }
        }
         
      
        int l = 0, r = -1;   
        long res = 0;
        if(cntb == 0 || cnta == 0) res = 0; 
        else
            for(int i = 0; i < cnta; i++){
                // 先判断r+1是不是在范围内, 再 r++;
                while(r + 1 < cntb && B[r + 1] <= A[i] + 5 + k)
                	r++;
                // 找到第一个在合法区间内的Bob
                while(l <= r && B[l] + 3 + k < A[i])
                	l++;
                	
                res += r - l + 1;
            }
        out.write(res + "");
        out.flush();
    }
}

I

在这里插入图片描述在这里插入图片描述
题目链接:
https://www.acwing.com/problem/content/1249/

这道题看着很简单,我刚开始做的时候想当然的将数组从大到小排序,然后加上n个最大的数之后,减去剩下的数。

但是这种思路是错的

比如数据:
1 3
5 4 3 2 1

如果按照刚才的思路,答案是5 + 4 - 3 - 2 - 1 = 3

而正确答案是 5 + 4 - (1 - 2 - 3) = 13

对应的一种后缀表达式为:5 4 + 1 2 - 3 - -

因为题目是让我们自己构造后缀表达式,也就是说我们可以随便改变n+m+1个数的运算顺序,即我们可以在对应的中缀表达式上任意加括号。

有n + m +1 个数,n个加号,m个减号。

如果只有加减操作,则k个数的最大值一定是k个数相加。所以说,我们要尽量使负号变为正号,即尽量减少减号的个数。

如果不存在负数,我们必须要减去正数。
因为负负得正,所以为了使结果最大,我们可以用一个减号使其他m-1个减号变为正号。即我们要构造出这样的中缀表达式:

A + B - (C - D - E - F - G)

也就是:
A + B + D + E + F + G - C

即只需要减去一个最小的数。

如果存在负数,我们直接减去负数,使其变为正数。如果减号不够,就将其加入括号中。

所以最后无论怎么样都可以构成这样的式子:

A + B + D + E + F + G - C

但是只有一个数的正负不能被改变,就是A,因为只要有减号,就不能没有被减数。为了使结果最大,A要确保是最大数。

代码:

import java.io.*;
import java.math.*;

public class Main{
    static BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
    static BufferedWriter out = new BufferedWriter(new OutputStreamWriter(System.out));
    
    public static int Int(String s){
        return Integer.parseInt(s);
    }
    
    public static void main(String[] args) throws Exception{
        String[] s = in.readLine().split(" ");
        int n = Integer.parseInt(s[0]);
        int m = Integer.parseInt(s[1]);
        
        String[] arr = in.readLine().split(" ");
        
        int len = n + m + 1;
        long sum = 0;
        if(m == 0){ // 全是加号
            for(int i = 0; i < len; i++)
                sum += Integer.parseInt(arr[i]);
        }
        else{
            int max = 0, min = 0;
            
            for(int i = 0; i < len; i++){
                if(Int(arr[min]) > Int(arr[i]))
                    min = i;
                if(Int(arr[max]) < Int(arr[i]))
                    max = i;
            }
            
            sum = Int(arr[max]) - Int(arr[min]);
            
            for(int i = 0; i < len; i++){
                if(i != min && i != max)
                    sum += Math.abs(Int(arr[i]));
            }

        }
        out.write(sum + "");
        out.flush();
    }
    
}

J

在这里插入图片描述
在这里插入图片描述在这里插入图片描述在这里插入图片描述在这里插入图片描述
参考yxc题解。视频讲解:https://www.bilibili.com/video/BV1Mb411t7M1

我这里大致说一下思路:

由题目中给的操作我们可以发现,对一个 a i a_i ai 进行一次灵能传输,对其前缀和的影响是交换了 S i 和 S i − 1 的 位 置 S_i 和 S_{i-1}的位置 SiSi1

即一次灵能传输对前缀和的影响是:

( S i − 1 , S i , S i + 1 ) (S_{i-1},S_i ,S{i+1}) Si1SiSi+1 — > ( S i , S i − 1 , S i + 1 ) (S_{i},S_{i-1} ,S{i+1}) SiSi1Si+1

我们要求的就是通过交换 [ S 1 . . . S n − 1 ] [S_1 ...S_n-1] [S1...Sn1]使得 ∣ S 1 − 0 ∣ ∣ S 2 − S 1 ∣ . . . ∣ S n − S n − 1 ∣ |S_1 - 0| |S_2 - S_1|...|S_n - S_{n-1}| S10S2S1...SnSn1的中的最大值达到最小。注意 S n S_n Sn 的位置不能变,因为无论怎么操作, S n S_n Sn 的值始终不变。又因为 S 1 S_1 S1 的值一定会减去0,所以我们直接将 S 0 设 为 0 S_0 设为 0 S00 ,并且 S 0 和 S n S_0 和 S_n S0Sn的位置是不能变的。

如果排完序后,0是最小值, S n S_n Sn是最大值,那么我们直接遍历整个前缀和数组,得到的最大差值的绝对值,就是答案。因为在此种前缀和的排列方式中,无法找到另一种排列方式使得两个前缀和之间的差值变的更小。

比如 序列 0 1 2 3 4 5 6

改变其中任意两个数的位置,都会使差值变大。

但是如果有负数,或者{S_n}不是最大值,直接排序就会出错。

比如 序列 : -3 -2 -1 0 2 3 5, S 0 = 0 , S n = 2 S_0 = 0, S_{n} = 2 S0=0,Sn=2

正确的排列方式:0 -2 -3 -1 3 5 2

答案为 4

为什么这样排列呢, 因为 0 要先 到达最小值 然后由最小值到达最大值,再由最大值到达 S n S_n Sn是最优的, 如果 0 下一个数排成比0大的数,那么该数后面的差值就会更大。

然后为了使从0到最小值和从最小值到最大值的差值尽可能的小,我们不能直接将所有小于0大于最小值的的数全都排到它俩中间,比如0 -1 -2 -3 2 3 5
这样排到正数的时候就会使差值更大,为了缓冲最小值到达正数的部分,
我们要隔一个数排,比如 0 之后 排-2 而不排-1 ,-1 留作缓冲最小值和正数部分。

如果隔两个数排,0 和 第三个数的差值又变大了。所以最优情况就是将前缀和序列排好序后, 从 0 往前取数, 每隔一个取一个, 从Sn往后取数,每隔一个取一个,然后再中间填上剩余的数。之后遍历整个排列后的序列,求最大差值的绝对值就是答案。

想不明白隔一个取一个的可以手动模拟一下数据。

代码:

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

public class Main{
    static BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
    static BufferedWriter out = new BufferedWriter(new OutputStreamWriter(System.out));
    
    static final int N = 300010;
    
    static long[] S = new long[N];
    static int[] st = new int[N];
    
    public static long Int(String s){
        return Long.parseLong(s);
    }
    
    public static void main(String[] args) throws IOException{
        int T = Integer.parseInt(in.readLine());
        
        while(T != 0){
            T --;
            int n = Integer.parseInt(in.readLine());
            
            String[] s = in.readLine().split(" ");
            
            S[0] = 0;
            
            for(int i = 1; i <= n; i++){
                S[i] = Int(s[i-1]); // 得到前缀和数组
                S[i] += S[i-1];
            }
            
            long s0 = 0, sn = S[n]; // 记录s0 和 sn 的值,以便排序后找到他们的位置
            
            if(s0 > sn){ // 对于sn < 0的情况,我们将其值调换,仍作为s0是最小值 sn是最大值处理。
                long temp = s0;
                s0 = sn;
                sn = temp;
            }
            
            Arrays.sort(S, 0, n + 1);
            
            for(int i = 0; i <= n; i++){
                if(s0 == S[i]){
                    s0 = i;
                    break;
                }
            }
            
            for(int i = 0; i <= n; i++){
                if(sn == S[i]){
                    sn = i;
                    break;
                }
            }
            
            long[] a = new long[N];
            
            Arrays.fill(st, 0);
            int l = 0, r = n;
            // 隔一个取一个
            for(int i = (int) s0; i >= 0; i -= 2){
                a[l ++] = S[i];
                st[i] = 1;
            }
                
            for(int i = (int) sn; i <= n; i += 2){
                a[r --] = S[i];
                st[i] = 1;
            }
                
            for(int i = 0; i <= n; i++){
                if(st[i] == 0)
                    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]));    
            }
            
            out.write(res + "\n");
            out.flush();
        }
    }
}
已标记关键词 清除标记
©️2020 CSDN 皮肤主题: 游动-白 设计师:上身试试 返回首页