蓝桥杯 java 历届试题 对局匹配

题目简述

原题见:蓝桥杯官网
大意是对输入的n个数,求最多有多少个数,满足这些数的差不等于k。n、k为输入值。其中,n、k和数值的大小都在0~100000之间。

暴力搜索方法

由于之前一直在做搜索,我第一个想到的就是搜索,但是由于n限制最大在10W,明显会超时,不过为了熟练最近训练的搜索算法,还是尝试实现了动态搜索+回溯的方法,然而没想到的是不仅超时了,同时还报错了!但网上给的两组测试集确能通过,在查找错误的时候才知道,原来递归的堆栈是有上限的,这个上限是不确定的(与内存有关,但堆栈溢出不一定会导致内存用满),对于最简单的上限在本人电脑上测试大约2W次就会溢出,稍微复杂一点的递归就会降成5000次左右。
最终在蓝桥杯测评集里,第一组数据正确,2、3组数据超时,其他全是运行错误(大概是堆栈溢出)。

import java.io.BufferedReader;
import java.io.InputStreamReader;

/**程序大概不是错的,应该是堆栈溢出*/
public class Main_ver1 {
	
	public static void main(String[] args) throws Exception {
		
		BufferedReader br = new BufferedReader(
				new InputStreamReader(System.in));
		String[] nk = br.readLine().split(" ");
		n = Integer.parseInt(nk[0]);
		k = Integer.parseInt(nk[1]);
		String[] str = br.readLine().split(" ");
		nums = new int[n];
		
		
		for(int i = 0; i < n; i ++) {
			nums[i] = Integer.parseInt(str[i]);
		}
		/*
		将上面的for循环去除,用下面的for循环,用以下数据测试:会得到:java.lang.StackOverflowError
		100000 0
		1 2 3 4
		*/
//		for(int i = 0; i < n; i ++) {
//			nums[i] = i;
//		}
		
		br.close();
		str = null;
		repeat = new int[100005];
		System.out.println(dp(0, 0));
		
	}
	
	static int[] repeat;     // 是否会重复
	static int maxNum = 0;   // 最大数量
	static int n;            // 总人数
	static int k;
	static int[] nums;
	
	static int dp(int curNum, int curCount){ // curNum表示递归的位置 curCount表示当前有多少人
//		System.out.println(curNum + " " + curCount);
		if(maxNum > curCount + (n - curNum))
			return 0;
		if(curNum == n)
			return curCount;
		maxNum = maxNum>curCount? maxNum:curCount;
		int a = 0;
		if(accept(nums[curNum])) { // 如果这个人的加入不会破坏约束
			addOne(nums[curNum]);
			a = dp(curNum + 1, curCount + 1);
			removeOne(nums[curNum]); // 回溯
		}
		int b = dp(curNum + 1, curCount);
		return a>b? a:b;
	}
	
	/**index是否能加入*/
	static boolean accept(int index) {
		return repeat[index] == 0;
	}
	
	/**加入index后,改变向量*/
	static void addOne(int index) {
		int a = index + k;
		if(a < 100000)
			repeat[a] ++;
		int b = index - k;
		if(b > -1)
			repeat[b] ++;
	}
	
	/**移除某一个向量*/
	static void removeOne(int index) {
		int a = index + k;
		if(a < 100000)
			repeat[a] --;
		int b = index - k;
		if(b > -1)
			repeat[b] --;
	}
	
}

正确的食用方式

考虑输入为a的数值,其能不能出现只与a-k和a+k数值是否出现有关,考虑搜索的方向,则a能不能出现只需要考虑a-k是否出现,这样能将问题转化为:在一组数中,求各不相邻的一组数的和的最大值,下面是求解这个最大值得思路:
这里考虑动态规划,当已经确定从头到当前位置前一个位置是最优的时候,那么当前位置的最优值只有两个可能:
1.当前位置前2个位置的最优和值加上当前位置的值。
2.当前位置前1个位置的最优和值。
因此对于这道题可以先求出前0到k组最优和结果,k到2k最优和的结果,然后根据这个结果循环判断就好了。
注意:对于k=0的情况需要特殊处理。

import java.io.BufferedReader;
import java.io.InputStreamReader;

/**对局匹配
 * 对于val数值能否登录实际上相关的只有val-k和val+k两个数值 如果设定数值是递增的,那么只有val+k有关系*/
public class Main {
	
	public static final int MAX = 100005;
	
	public static void main(String[] args) throws Exception {
		
		BufferedReader br = new BufferedReader(
				new InputStreamReader(System.in));
		String[] nk = br.readLine().split(" ");
		Integer.parseInt(nk[0]);  // 人数 没有用到
		int k = Integer.parseInt(nk[1]);  // 间隔
		String[] str = br.readLine().split(" ");
		br.close();
		
		int[] repeat = new int[MAX];
		for(int i = 0; i < str.length; i ++) {
			repeat[Integer.parseInt(str[i])] ++;
		}
		str = null;
		
		/*k为0 特殊处理*/
		if(k == 0) {
			int count = 0;
			for(int i = 0; i < MAX; i ++) {
				if(repeat[i] > 0) {
					count ++;
				}
			}
			System.out.println(count);
			return;
		}
		
		int k2 = 2*k;
		int[] dp = new int[MAX];
		/*处理0~k*/
		for(int i = 0; i < k; i ++) {
			dp[i] = repeat[i];
		}
		/*处理k~2k*/
		for(int i = k; i < k2; i ++) {
			dp[i] = Math.max(dp[i - k], repeat[i]);
		}
		/*根据之前的计算生成后续*/
		for(int i = k2; i < MAX; i ++) {
			dp[i] = Math.max(
					dp[i - k],
					dp[i - k2] + repeat[i]);
		}
		/*统计*/
		int count = 0;
		for(int i = MAX-1-k; i < MAX-1; i ++) {
			count += dp[i];
		}
		System.out.println(count);
		
	}
	
}

你可能感兴趣的:(蓝桥杯,java)