题目
AcWing 3583 整数分组
给定 n 个整数
a
1
,
a
2
,
…
,
a
n
a_1,a_2,…,a_n
a1,a2,…,an。
现在,请你从中挑选一些数,并将选出的数进行分组。
要求:
- 选出的数最多划分为 k 组(至少 1 组)。
- 同一组内,任意两数之差的绝对值不超过 5。
- 所选出的数尽可能多。
请问,最多可以选出多少个数进行分组?
输入格式
第一行包含两个整数 n 和 k。
第二行包含 n 个整数
a
1
,
a
2
,
…
,
a
n
a_1,a_2,…,a_n
a1,a2,…,an。
输出格式
输出一个整数,表示可以选出的最大整数数量。
数据范围
1
≤
k
≤
n
≤
5000
1≤k≤n≤5000
1≤k≤n≤5000,
1
≤
a
i
≤
1
0
9
1≤a_i≤10^9
1≤ai≤109
输入样例1:
5 2
1 2 15 15 15
输出样例1:
5
输入样例2:
6 1
36 4 1 25 9 16
输出样例2:
2
输入样例3:
4 4
1 10 100 1000
输出样例3:
4
思路
首先对于全部数可以进行排序。
首先我们看看最优解具有什么性质
能够用dp需要是一个序列,而这里是一个集合,看看如何在这里把它变成一个序列
可以发现最优解可以具有如下的性质:
- 对于两个数,如果满足条件(差小于5),那么中间的数一定都可以加入到这个组里(即可选也可以不选),因为每一组是否满足条件只是看最大值与最小值是否满足要求,与中间的值没有关系,那么为了最优解肯定都是都加入到解里面。
- 最优解中会不会有区间重合?最优解中可以有区间重合,但是比如两个组有区间重合,可以分成两个没有区间重合的组,仍满足最优解,所以最优解可以没有区间重合。
- 对于最后一段,最优解中,这一组可以尽可能的长。(这个性质主要是为了用于状态转移)
即蓝色的是本来的最优解,我们调整这选的两个组,把前面的一组中的满足最后面一组的部分分一部分去,这样仍然满足最优解,且最后一组尽可能长了。选的元素个数也没有变。
所以最优解我们可以在这个集合的满足以上性质的子集里面去寻找,最优解在这个子集中一定存在,而满足以上性质的情况下就可以用dp来求解。
这样子选就是一段一段的选,那么就是一个个序列了,所以就可以用dp来做了。
我们可以用
f
(
i
,
j
)
f(i,j)
f(i,j)来进行状态表示,它表示的集合是从
1
−
i
1-i
1−i 中选取
j
j
j 组的所有方案的集合,所存的数表示的属性是这个集合中的方案中的最大的元素个数。
对于状态计算,我们对于集合进行划分。
分为在
1
−
i
1-i
1−i 中选取第
i
i
i 个和不选取
i
i
i 个两个子集。
对于不选取第
i
i
i 个的情况,那么这个集合所取的属性(即最大数)就是
f
[
i
−
1
]
[
j
]
f[i-1][j]
f[i−1][j]
对于选取第
i
i
i 个的情况,根据上面的性质,那么最后一个区间可以尽可能的大。
肯定都得选最后一组,那么最大值就是看前面。
所以最大值就是
f
[
k
−
1
]
[
j
−
1
]
+
(
i
−
k
+
1
)
f[k - 1][j - 1] + (i - k + 1)
f[k−1][j−1]+(i−k+1)
这里求得
k
k
k 可以用双指针扫描。
然后取两种情况的最大值即可。
然后最终答案是 f [ n ] [ m ] f[n][m] f[n][m]的值,这里分成 m m m 组是否是最优的呢?这里 m < n m<n m<n,可以是一种方案,最优解可以是分成 m m m 组,也可以不是 m m m 组,对于不是 m m m 组的最优解,实际上可以化成 m m m 组的情况,把组分开成多组就可以了。
代码
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 5010;
int n, m;
int w[N];
int f[N][N];
int main()
{
scanf("%d%d", &n, &m);
for (int i = 1; i <= n; i ++ ) scanf("%d", &w[i]);
sort(w + 1, w + n + 1);
for (int i = 1, k = 1; i <= n; i ++ )
{
while (w[i] - w[k] > 5) k ++ ;
for (int j = 1; j <= m; j ++ )
f[i][j] = max(f[i - 1][j], f[k - 1][j - 1] + (i - k + 1));
}
printf("%d\n", f[n][m]);
return 0;
}