题目
有n个人参加面试,公司只需要m个人,于是将n个人分成m段每段⌊nm⌋人,且从每段中选出能力最大的人。有n个人参加面试,公司只需要m个人,于是将n个人分成m段每段⌊nm⌋人,且从每段中选出能力最大的人。
现在要求计算出一个最小的m满足选出的m人的能力和严格大于k。现在要求计算出一个最小的m满足选出的m人的能力和严格大于k。
题解
Step1Step1
网上很多使用二分的方法
其实是不对的,我们可以讨论一下二分为什么不对。
思路就是二分m,去判断选出的m人的和是否大于k思路就是二分m,去判断选出的m人的和是否大于k
大于说明当前m是满足的,于是更改上界,再去判断是否存在更小的m也满足。大于说明当前m是满足的,于是更改上界,再去判断是否存在更小的m也满足。
否则就更改下界,去寻找更大的m满足条件。否则就更改下界,去寻找更大的m满足条件。
这里默认了一个条件即m越大获得能力和越大。这也是能使用二分的单调性。这里默认了一个条件即m越大获得能力和越大。这也是能使用二分的单调性。
乍一看没什么不对,选的人多了嘛,肯定能力和越大。
可是实际上并不满足这个性质,因为题目是从一段中选出一个最大的。
比如例子3,4,55,55,2,3比如例子3,4,55,55,2,3
当m=2时,答案为55+55=110;m=3时答案为4+55+3=62当m=2时,答案为55+55=110;m=3时答案为4+55+3=62
并不满足单调递增性(m越大和越大)并不满足单调递增性(m越大和越大)
所以二分是错误的,但是二分能过只能说数据不强,或者出题人有意放过。所以二分是错误的,但是二分能过只能说数据不强,或者出题人有意放过。
Step2Step2
那么接下来就来讨论正确的做法
既然不能二分,那只能从小到大枚举m了吧既然不能二分,那只能从小到大枚举m了吧
使用ST表查询时间是O(1)的那么朴素的枚举m的时间复杂度为使用ST表查询时间是O(1)的那么朴素的枚举m的时间复杂度为
但是这里存在一个优化但是这里存在一个优化
我们知道⌊nm⌋只有n−−√种值。我们知道⌊nm⌋只有n种值。
也就是说对于不同的m,⌊nm⌋是一样的,即每一段的人数是一样的。也就是说对于不同的m,⌊nm⌋是一样的,即每一段的人数是一样的。
于是对于相同的⌊nm⌋我们没必要从头开始,只需要继续累计和即可于是对于相同的⌊nm⌋我们没必要从头开始,只需要继续累计和即可
例子n=7例子n=7
假设我们已经枚举到了m=4,⌊nm⌋=1假设我们已经枚举到了m=4,⌊nm⌋=1
也就是每1个人取一个最大值,现在已经取得了前4个人的值。也就是每1个人取一个最大值,现在已经取得了前4个人的值。
当m=5,,⌊nm⌋=1还是每1个人取一个最大值。当m=5,,⌊nm⌋=1还是每1个人取一个最大值。
我们已经知道前4个人的最大值的和,对于第5个,只需要加上即可。我们已经知道前4个人的最大值的和,对于第5个,只需要加上即可。
而对于⌊nm⌋出现的范围是[m,n⌊nm⌋]而对于⌊nm⌋出现的范围是[m,n⌊nm⌋]
时间复杂度为
代码
#include<bits/stdc++.h>
using namespace std;
const int maxn = 2e5+10;
typedef long long ll;
int arr[maxn],mm[maxn];
int n;ll k;
struct ST{
int dp[maxn][20];
void init() {
mm[0] = -1;
for(int i=0;i<n;i++) {
dp[i][0] = arr[i];
mm[i+1] = (((i+1) & i) == 0) ? mm[i] + 1 : mm[i];
}
for(int j=1;(1<<j)<=n;j++) {
for(int i=0;i+(1<<j)-1<n;i++) {
dp[i][j] = max(dp[i][j-1],dp[i+(1<<(j-1))][j-1]);
}
}
}
int query(int l,int r) {
int k = mm[r-l+1];
// while((1<<(k+1)) <= (r - l + 1)) k++;
return max(dp[l][k],dp[r-(1<<k)+1][k]);
}
}st;
int solve() {
st.init();
for(int m=2;m<=n;) {
int len = n / m,c = 1,l;
int r = n / len;
ll res = 0;
for(;m<=r;m++) {
for(;c<=m;c++) {
l = c * len;
res += st.query(l-len,l-1);
if(res > k) return m;
}
}
}
return -1;
}
int main()
{
while(~scanf("%d%lld",&n,&k),n != -1 && k != -1) {
ll sum = 0;
int tmp = 0;
for(int i=0;i<n;i++) {
scanf("%d",&arr[i]);
sum += arr[i];
tmp = max(tmp,arr[i]);
}
if(sum <= k) {
printf("-1\n");
continue;
}
if(tmp > k) {
printf("1\n");
continue;
}
printf("%d\n",solve());
}
return 0;
}
博客围绕面试选人问题展开,公司从n个人中选m人,将n人分m段,每段选能力最大者。要求计算最小的m使选出的m人能力和大于k。指出网上用二分法求解有误,因其不满足单调性,正确做法是从小到大枚举m,还介绍了利用⌊n/m⌋取值优化枚举的方法。
232

被折叠的 条评论
为什么被折叠?



