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;
}