传送门: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;
}