HDU - 3486 Interviewe RMQ优化

博客围绕面试选人问题展开,公司从n个人中选m人,将n人分m段,每段选能力最大者。要求计算最小的m使选出的m人能力和大于k。指出网上用二分法求解有误,因其不满足单调性,正确做法是从小到大枚举m,还介绍了利用⌊n/m⌋取值优化枚举的方法。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

题目

nmnmnm有n个人参加面试,公司只需要m个人,于是将n个人分成m段每段⌊nm⌋人,且从每段中选出能力最大的人。
mmk现在要求计算出一个最小的m满足选出的m人的能力和严格大于k。


题解

Step1Step1
网上很多使用二分的方法
其实是不对的,我们可以讨论一下二分为什么不对。
m,mk思路就是二分m,去判断选出的m人的和是否大于k
mm大于说明当前m是满足的,于是更改上界,再去判断是否存在更小的m也满足。
m否则就更改下界,去寻找更大的m满足条件。
m使这里默认了一个条件即m越大获得能力和越大。这也是能使用二分的单调性。
乍一看没什么不对,选的人多了嘛,肯定能力和越大。
可是实际上并不满足这个性质,因为题目是从一段中选出一个最大的。
3,4,55,55,2,3比如例子3,4,55,55,2,3
m=255+55=110;m=34+55+3=62当m=2时,答案为55+55=110;m=3时答案为4+55+3=62
(m)并不满足单调递增性(m越大和越大)
所以二分是错误的,但是二分能过只能说数据不强,或者出题人有意放过。

Step2Step2
那么接下来就来讨论正确的做法
,m既然不能二分,那只能从小到大枚举m了吧
使STO(1)m使用ST表查询时间是O(1)的那么朴素的枚举m的时间复杂度为

O(n2)O(n2)

但是这里存在一个优化
nmn我们知道⌊nm⌋只有n种值。
mnm也就是说对于不同的m,⌊nm⌋是一样的,即每一段的人数是一样的。
nm于是对于相同的⌊nm⌋我们没必要从头开始,只需要继续累计和即可

n=7例子n=7
m=4,nm=1假设我们已经枚举到了m=4,⌊nm⌋=1
14也就是每1个人取一个最大值,现在已经取得了前4个人的值。
m=5,nm=11当m=5,,⌊nm⌋=1还是每1个人取一个最大值。
4,5我们已经知道前4个人的最大值的和,对于第5个,只需要加上即可。
nm[m,nnm]而对于⌊nm⌋出现的范围是[m,n⌊nm⌋]
时间复杂度为

O(nlog(n))O(nlog(n))

代码

#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;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值