题目大意:
有n个人,每个人有一个权值,现在要求选出k个队伍,每个队伍内部最大权值和最小权值差值不能超过5,问选k出队伍最多包含多少人,队伍不能为空且不能有交集.可以有人不选
首先解决第一个问题,可能的队伍情况,枚举每个点i为队伍内最小值或者最大值,则可以得到点i所在的队伍的人数。接下来问题变成,选出k段不相交的区间使得区间长度总和最大。似乎可以贪心或者dp。考虑dp,如果我们前面选的是每个点i是i所在队伍的最小值,那么排序后,变成以i点为起点的向后的一段连续区间,如果想要dp,似乎不能用填表法,而只能用刷表法。
反过来,我们也可以使用以i为终点的这种模型,其含义是,i为队伍内最高权值,向前找所能组成的队伍的人数,我们可以用二分找出,队伍的区间范围。这样做的好处是可以用填表法来解dp了,且转移方程很容易得出。
考虑决策过程:对于第i个人,是否选第i个人所在的队伍。这个决策过程与背包问题是相似的。
因此可以设计出状态:dp[i][j] 表示前i个人,选j支队伍的最大总人数。
转移方程 dp[i][j] = max(dp[i-1][j],dp[i - num[i]][j-1]+num[i]),即选和不选,若不选,则在前i-1个人选出j支队伍,若选,则要在前i-num[i]个人中选出j-1支队伍。dp[n][k]即为所有答案.
(在尝试写刷表的时候发现刷表并不是很好写,进而转填表)
思考正确性:大概可以想到,一段区间既是连续(可以一路连到起点),也是被拆开的(该区间内各个点为起点实际上是对这样一段连续的区间的分解),因此可以使用拆开的区间拼凑出任意合法的连续区间,从而得到答案。
#include<bits/stdc++.h>
using namespace std;
const int maxn = 5e3+10;
int n,k,a[maxn];
int num[maxn];
int dp[maxn][maxn];
vector<int> g;
int main(){
scanf("%d%d",&n,&k);
for(int i = 1; i <= n; i++)
scanf("%d",&a[i]);
sort(a+1,a+n+1);
for(int i = 1; i <= n; i++){
int low = a[i] - 5;
num[i] = i+1-(lower_bound(a+1,a+n+1,low)-a);
}
int ans = 0;
for(int i = 1; i <= n; i++){
for(int j = 1; j <= k; j++){
dp[i][j]=max(dp[i][j],dp[i-1][j]);
dp[i][j]=max(dp[i][j],dp[i-num[i]][j-1]+num[i]);
}
}
printf("%d\n",dp[n][k]);
}