CF_div3_1133_problem_E: K Balanced Teams(排序+二分+背包dp)

题目大意:
有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]);
}

猜你喜欢

转载自blog.csdn.net/qq_41997978/article/details/88679732