蓝桥杯2017年第八届真题-对局匹配

题目

题目链接

题解

动态规划。


题目大意:
在一个具有 n n n个元素的集合中选取尽可能多的数,保证这些数不存在两个数相差为 k k k,求个数。
(题目讲的乱七八糟)


首先,我们统计每个数的个数,c[i]表示有多少个i
对于每个数,都可以选或者不选,若我们不选数 i − k i-k ik,则我们可以选数 i i i,也可以不选数 i i i;但若我们选数 i − k i-k ik,则我们绝不能选数 i i i了。还有一种很奇怪的情况,当数 i i i的个数为0时,我们根本就无法选这个数,因此我们完全可以选数 i − k i-k ik或者不选数 i − k i-k ik


dp[i][0/1]表示数 i i i选或者不选时, i i i的对应位上最多能选个数;0表示不选,1表示选;(这个对应位是我自己起的个名字,下面有讲解)
转移方程:
dp[i][0] = max(dp[i-k][0], dp[i-k][1])
dp[i][1] = max(dp[i-k][0], dp[i-k][1]) 当数 i i i的数量为0时
dp[i][1] = dp[i-k][0] + c[i] 当数 i i i的数量不为0时
初始化:我们只要初始化好最前面的 k k k个就可以推出后面的了;前 k k k个数(从集合中出现的最小值开始数k个数)中,若这个数在集合中不存在,则初始化为0,否则初始化为出现次数。
答案:最后 k k k个数(从集合中出现的最大值开始向前数k个数)中,对于每个数选或不选的最大dp值之和。之所以答案是这个,是因为我们要找到0 ~ k-1的对应位上的最大dp值,对这些值累加,这就是我们的答案。


这个对应位,真是不好讲,准确的说并没有很严谨的定义。

i i i的对应位就是 i + k i+k i+k i + 2 ∗ k i+2*k i+2k、……、 i − k i-k ik i − 2 ∗ k i-2*k i2k、……
以下面的样例的初始化为例,

10 3
0 0 1 1 3 3 4 6 7 7

初始化前3个,即0、1、2;
dp[0][1] = 2, dp[1][1] = 2, dp[2][1] = 0
dp[0][0] = 0, dp[1][0] = 0, dp[2][0] = 0
dp[3][1]保存的是在数1和数3中进行选择,保证最终集合中不能出现两个数相差为3的集合个数最大值;
同理,dp[6]保存的是在数1、数3和数6中进行选择,保证最终集合中不能出现两个数相差为3的集合个数最大值。

代码

#include<bits/stdc++.h>
using namespace std;
const int N = 100010;
typedef long long ll;

int n, k, mx, mn = N, x;
ll dp[N][2], ans, c[N];

int main()
{
	cin>>n>>k;
	for(int i = 1;i <= n;i ++) cin>>x, c[x] ++, mx = max(x, mx), mn = min(x, mn);
	
	if(k == 0) { // k为0时特判,答案等于不同数的个数 
		for(int i = mn;i <= mx;i ++) ans += bool(c[i]);
		cout << ans << endl;
		return 0;
	}
	
	for(int i = mn;i <= min(mn+k-1, mx);i ++) dp[i][0] = 0LL, dp[i][1] = c[i]; // 初始化dp[mn ~ mn+k-1],若选索引的值这个数,则数量为统计的数量,若不选则为0;取min是为了防止超出mx,即最大范围 
	for(int i = min(mn+k-1, mx) + 1;i <= mx;i ++) { // 动态规划 
		dp[i][0] = max(dp[i-k][0], dp[i-k][1]); // 若不选i这个数,则值为i-k的数可以选,也可以不选 
		
		// 选i这个数 
		if(c[i]) dp[i][1] = dp[i-k][0] + c[i]; // 若i这个数的个数不为0,选i就不能选i-k 
		else dp[i][1] = max(dp[i-k][0], dp[i-k][1]); // 若i这个数的个数为0,则i-k这个数还是可以选的,这与dp[i][0]一致了 
	} 
	
	for(int i = max(mx-k+1, mn);i <= mx;i ++) ans += max(dp[i][0], dp[i][1]); // 统计最后k个的dp和;取max是为了防止不大于mx的数的总个数不足k个 
	
	cout << ans << endl;
	
	return 0;
}

  • 3
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

不牌不改

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值