【二分答案】【动态规划】【扫描线】人以群分

 传送门:https://nanti.jisuanke.com/t/36107
某班有 n 个同学,每个同学有一个外向程度 ai
​ 。由于要进行某个活动,需要把他们分成若干个小组,每个小组的人数至少为 m 人。不同外向程度的人在一个小组会产生不开心值,定义一个小组的不开心值为组内成员外向程度最大值和最小值的差,一个班级的不开心值为所有小组不开心值的最大值。

那么问题来了,如何分组使得班级的不开心值最小,请你求出这个最小的班级不开心值。

输入格式
第一行两个整数 n,m,分别表示人数和每个小组最少的人数要求。

第二行 n 个整数 a_i
​ ,表示每个同学的外向程度。

输出格式
一个整数,表示最小的班级不开心值。

数据范围
对于 30%30% 的数据:1\le m \le n \le 201≤m≤n≤20,1\le a_i \le 1001≤a
i≤100。

对于 60%60% 的数据:1\le m \le n\le 10001≤m≤n≤1000,1\le a_i \le 10001≤a
i ≤1000。

对于 100%100% 的数据:1<=m<=n<=5E5,1<=ai<=1E9


 本题出自计蒜客蓝桥模拟赛。
 很好的一道题。首先像这种最大值的最小值,一定是要用二分答案的,重点就是怎么check。尝试了各种贪心的check都不行,于是想到了dp。
 所有数据首先经过排序。 我们如果用dp[i]表示前i个数字在猜测答案x的条件下,是否能够划分成比m大的子集和。那么dp[i]可以去把l…r-1的一串数字染成true,l=i+m,r是第一个不满足a[r]-a[i+1]<=x的位置(当然r可能小于或等于l,这时候不染色)。
 每check一次dp一次,返回的是dp[n]是否为true。
 然而这样的话dp最差是n^2效率,可以想到用树状数组+扫描线或者线段树来改进。但是五十万的数据不允许两个log存在。实际上可以发现由于我们的点查询是从左到右连续的,所以没必要搞树状数组,直接扫描线就行了,然后dp[i]就是i处的覆盖区间数,也就是扫描线数组的前缀和,前缀和大于0相当于为true,否则为false。

#include<cstdio>
#include<algorithm>
#include<queue>
#define M (L+R>>1)
using namespace std;

int n,a[500005],m,L,R,dp[500005];

bool check(int x)
{
	dp[0]=1;
	dp[1]=-1;
	for(int i=2;i<=n;i++)
		dp[i]=0;
	int pre=0;
	for(int i=0,j=1,l,r;i<=n;i++)
	{
		pre+=dp[i];
		if(!pre)
			continue;
		while(j<=n&&a[j]-a[i+1]<=x)
			j++;
		l=i+m;
		r=j;
		if(l>=r)
			continue;
		dp[l]++;
		dp[r]--;
	}
	return pre>0; 
}

int main()
{
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;i++)
		scanf("%d",&a[i]);
	sort(a+1,a+n+1);
	L=0;
	R=a[n]-a[1];
	while(L<R)
		if(check(M))
			R=M;
		else
			L=M+1;
	printf("%d",L);	
	return 0;
}
  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 7
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值