[蓝桥杯][历届试题 PREV-50]对局匹配(Java)(动态规划)

本文探讨了一种在特定围棋网站上进行对局匹配的算法,该算法旨在找到最多可能在线但无法配对的用户数量,通过分组和动态规划解决复杂匹配问题。

 

 历届试题 对局匹配  

时间限制:1.0s   内存限制:256.0MB

    

问题描述

  小明喜欢在一个围棋网站上找别人在线对弈。这个网站上所有注册用户都有一个积分,代表他的围棋水平。


  小明发现网站的自动对局系统在匹配对手时,只会将积分差恰好是K的两名用户匹配在一起。如果两人分差小于或大于K,系统都不会将他们匹配。


  现在小明知道这个网站总共有N名用户,以及他们的积分分别是A1, A2, ... AN。


  小明想了解最多可能有多少名用户同时在线寻找对手,但是系统却一场对局都匹配不起来(任意两名用户积分差不等于K)?

输入格式

  第一行包含两个个整数N和K。
  第二行包含N个整数A1, A2, ... AN。


  对于30%的数据,1 <= N <= 10
  对于100%的数据,1 <= N <= 100000, 0 <= Ai <= 100000, 0 <= K <= 100000

输出格式

  一个整数,代表答案。

样例输入

10 0
1 4 2 8 5 7 1 4 2 8

样例输出

6


解题思路:

一般看到这种需要求最优解或者最优方案之类的题,一部分与动态规划有关(也不全是),

首先我们要对所有数据统计,每个数出现多少次。

对于所有数据求动态公式是一件难事,所以先要分组(k为0时除外),将连续相差为k的值分到一组,求组中的最大解:

       例如:k为3,那么第一组的数据有,0,3,6,9,12……

                            那么第二组的数据有,1,4,7,10,13……

                            那么第三组的数据有,2,5,8,11,14……

然后将每组中求得的最优解相加,答案就是所有数据的最优解。

================更新===============

因为数据的最大为100000,所以创建int[] val = new int[100000+1];,这样可以统计重复值的个数,也方便计算相差k时的数值。

反思错误:

①看到题想到动态规划,但是没有想到分组考虑,实现难度很大

②分组考虑后,以自认为简化的程序实现,结果超时。后来分析循环一百万次的赋值都比循环十万次的Math.max时间短

③忘记考虑有连续重复值情况(n=5,k=5,值为1,4,4,4,9)。

Java代码:

import java.util.Scanner;

public class Main {
	public static void main(String[] args) {
		int res = 0;
		Scanner cin = new Scanner(System.in);
		int N = cin.nextInt();
		int k = cin.nextInt();
		int[] val = new int[100001];
		//如果k不等于0,
		for(int i=0;i<N;i++) {
			val[cin.nextInt()]++;
		}
		if(k==0) {
			for(int i=0;i<100001;i++) {
				if(val[i]!=0) res++;
			}
		}
		else {
			int[] agroup = new int[100001];//一个组,每次循环,重新利用这个组
			int[] dp = new int[100001];
			//一共有k个组,循环每组,找出能过匹配的最大的人数
			for(int i=0;i<k;i++) {
				int len = 1;
				for(int j=i;j<100001;j+=k) {
					agroup[len++] = val[j];//当前组的第len个值为val[j]
				}
				
				dp[0] = 0;dp[1] = agroup[1];
				for(int j=2;j<len;j++) {
					dp[j] = Math.max(dp[j-1], dp[j-2]+agroup[j]);
				}
				res += dp[len-1];
			}
		}
		System.out.println(res);
		cin.close();
	}
}

测试结果截图:

================二次提交==========================

java代码(快读,核心代码在类SO中的sol函数):

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;
import java.util.StringTokenizer;


public class Main{
	public static void main(String[] args) {
//		long start = System.currentTimeMillis();
        
        
		InputStream is = System.in;
		OutputStream os = System.out;
		
		IN cin = new IN(is);
		PrintWriter cout = new PrintWriter(os);
		
		SO so = new SO();
		so.sol(cin,cout);
		
		cout.flush();
		
//		long end = System.currentTimeMillis();
//        System.out.println("using time:" + (end - start) + " ms");
	}
	
	static class SO{
		static final int MAX = 100000;
		void sol(IN cin,PrintWriter cout) {
			int n = cin.nextInt(), k = cin.nextInt();
			int[] nums = new int[MAX+1];
			int res = 0;
			//get nums
			for(int i=0;i<n;++i)
				++nums[cin.nextInt()];
			//k==0 
			if(k==0) {
				for(int i:nums)
					res+=i==0?0:1;
				cout.println(res);
				return;
			}
			//
			int[] dp = new int[MAX+1];
			for(int i=0;i<k;++i) {
				int top = 1;
				if(i<=MAX)dp[top++]=nums[i];//dp[1]
				for(int j=i+k;j<=MAX;j+=k) {
					dp[top] = Math.max(dp[top-1], dp[top-2]+nums[j]);
					++top;
				}
				res += dp[top-1];
			}
			cout.println(res);
			
		}//end sol
		
		
		
	}//end SO
	
	public static class IN{
		private BufferedReader reader;
		private StringTokenizer tokenizer;
		
		public IN(InputStream is) {
			reader = new BufferedReader(new InputStreamReader(is),32768);
			tokenizer = null;
		}
		
		public String next() {
			while(tokenizer==null || !tokenizer.hasMoreTokens()) {
				try {
					tokenizer = new StringTokenizer(reader.readLine());
				} catch (IOException e) {
					throw new RuntimeException(e);
				}
			}
			return tokenizer.nextToken();
		}
		
		public int nextInt() {
			return Integer.parseInt(next());
		}
	}//end IN
}

结果截图:

 

### 打家劫舍问题的动态规划解法 #### 问题分析 打家劫舍问题是经典的动态规划问题之一,其核心在于如何通过状态转移方程来计算最大收益。在本问题中,假设有一排房屋,每间房子里有一定金额的钱财,但是相邻的房子装有相互连通的防盗系统,如果两间相邻房子同一天被闯入,则会触发警报。因此,目标是在不触动警报的情况下,最大化能够偷取到的总金额。 #### 动态规划的核心要素 1. **定义状态** 定义 `dp[i]` 表示前 `i` 个房间所能获得的最大金额。 2. **状态转移方程** 对于第 `i` 个房间有两种选择: - 偷窃该房间,则无法偷窃第 `i-1` 个房间,此时总金额为 `dp[i-2] + nums[i]`。 - 不偷窃该房间,则当前最大金额继承自 `dp[i-1]`。 综合两种情况可得状态转移方程: \[ dp[i] = \max(dp[i-1], dp[i-2] + nums[i]) \] 3. **初始化条件** - 当只有第一个房间时,最大金额即为该房间的金额:`dp[0] = nums[0]`。 - 当有两个房间时,可以选择其中较大的一间作为初始值:`dp[1] = max(nums[0], nums[1])`。 4. **优化空间复杂度** 使用滚动数组的方式减少空间消耗,仅需两个变量存储前两次的状态即可完成计算。 #### 示例代码实现 以下是基于上述思路的 Python 实现: ```python def rob(nums): if not nums: return 0 elif len(nums) == 1: return nums[0] prev, curr = 0, 0 # 初始化滚动数组 for num in nums: temp = curr # 缓存当前值 curr = max(prev + num, curr) # 更新当前最大值 prev = temp # 更新前一个值 return curr # 测试用例 if __name__ == "__main__": houses = list(map(int, input("请输入各房间金额(逗号分隔):").split(','))) result = rob(houses) print(f"最大可偷金额为:{result}") ``` #### 解题思路总结 1. 题目要求的是在满足约束条件下找到全局最优解,这正是动态规划擅长处理的问题类型[^1]。 2. 动态规划的关键在于设计合适的状态表示以及推导出正确的状态转移方程。在此基础上,可以通过逐步填充表格或利用滚动数组降低时间与空间复杂度[^2]。 3. 在实际编程过程中,注意边界条件的处理,例如当输入为空列表或只有一个元素的情况下的特殊处理[^3]。 --- ###
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值