牛客小白80 D

Problem:牛客小白80D

思路

求n个人中有一个班级拖堂,跑K个人后,求在学校中人最多的班级的人数最少

解题方法

初步分析,需要求m个班级中少一个班级,把K分配之后使max(cnt[i])最小。
容易想到枚举哪个班级拖堂 O ( m ) O(m) O(m),再分配K,这里我们需要一个 O ( l o g n ) O(logn) O(logn)级别的方法。

暴力:那不就是待求序列排序后每次把最大的-1再排序,k(n)次操作。
解法1:那我们可以用树状数组来动态求前缀和来 O ( 1 ) O(1) O(1)求此时的解,再二分答案。
解法2:求一个 ∑ i = 1 , c n t [ i ] > = m i d m c n t [ i ] − m i d \sum_{i=1,cnt[i] >= mid}^m cnt[i]-mid i=1,cnt[i]>=midmcnt[i]mid,mid取1~n,这样也能 O ( 1 ) O(1) O(1)用来二分答案。求每个mid的这个求和解,当mid大于cnt[i]的时候是直接算0的,这里有个技巧可以使cnt[i]往前一项差分,求两次前缀和可以得到需要的数组。


复杂度

  • 时间复杂度:

O ( m l o g n ) O(mlogn) O(mlogn)

  • 空间复杂度:

添加空间复杂度, 示例: O ( n ) O(n) O(n)

解法1 Code

#include <bits/stdc++.h>

using namespace std;
#define int long long
#define lowbit(x) -x&x
const int N = 1e6+10;
int a[N], cnt[N];

int n, m ,k;
int tr[N][2];


void add(int x, int v, int t) {
    if(!x) return;
    
    while(x <= n) {
        tr[x][t] += v;
        x += lowbit(x); 
    }
}

int query(int x, int t) {
    int ans = 0;
    while(x) {
        ans += tr[x][t];
        x -= lowbit(x);
    }
    return ans;
}

void solve() {

    cin >> n >> m >> k;
    
    for(int i = 1; i <= n; i ++) {
        cin >> a[i];
        cnt[a[i]] ++;
    }
    for(int i = 1; i <= m; i++)add(cnt[i], 1, 0), add(cnt[i] ,cnt[i], 1);
   
    //exit(0);
    for(int i = 1 ;i <= m; i++) {
        if(n - cnt[i] < k) {cout << -1 << " ";continue;}
        
        add(cnt[i], -1, 0), add(cnt[i], -cnt[i], 1);
        int l = 0, r = n;
        while(l < r) {
            int mid = l + r>>1;
            int t = query(n, 0) - query(mid, 0);//l, r ->r~l-1
           
            if(query(mid, 1) + t*mid >= n - cnt[i]-k)r = mid;
            else l = mid+1;
        }
        cout << l << " ";
        add(cnt[i], 1, 0), add(cnt[i] ,cnt[i], 1);
    }
}

signed main() {
	
	ios::sync_with_stdio(0),cin.tie(0);
	solve();

	//system("pause");
	return 0;
}

解法2 Code

#include <bits/stdc++.h>

using namespace std;
#define int long long
const int N = 1e6+10, p = 998244353;
int a[N], cnt[N], cur[N];

int n, m ,k;
void solve() {

    cin >> n >> m >> k;
    
    for(int i = 1; i <= n; i ++) {
        cin >> a[i];
        cnt[a[i]] ++;
    }
    
    for(int i = 1; i <= m; i++) {
        if(cnt[i])cur[cnt[i]-1] ++;
    }
    for(int i = N-2; i >= 0; i--) cur[i] += cur[i+1];
    for(int i = N-2; i >= 0; i--) cur[i] += cur[i+1];
    
    for(int i = 1 ;i <= m; i++) {
        if(n - cnt[i] < k) {cout << -1 << " ";continue;}
        int l = 0, r = n;
        while(l < r) {
            int mid = l + r>>1;
            int now = cnt[i] - mid;
            if(now < 0) now = 0;
            if(cur[mid] - now <= k)r = mid;
            else l = mid+1;
        }
        cout << l << " ";
    }
}

signed main() {
	
	ios::sync_with_stdio(0),cin.tie(0);
	solve();

	//system("pause");
	return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值