CF2271E Array Emptying详细题解
思路
按数值从大到小贪心,如果能删就将该元素删去,如果不能删就将它分裂。
注意到元素的范围(\(1\le a_i \le n\)),开一个桶记录每个数字出现的次数,省去了排序的时间开销。
设要进行 \(x\) 次分裂操作,那么进行完 \(x\) 次分裂操作后元素数量为 \(n+x\),那么总的操作次数即为 \(n+x+x\)。
不难发现分裂次数 \(x\) 具有单调性,我们考虑二分答案。
inline bool check(int mid){
int kk=k+2*mid+n-1;
for(int i=1;i<=n;i++) cntt[i]=cnt[i];
int shu=n,tot=0;
while(shu){
if (cntt[shu]==0){
shu--;
continue;
}
if (shu>kk){
if (tot+1>mid) return false;
cntt[shu]--;
tot++;
cntt[(shu+1)/2]+=2;
}else{
cntt[shu]--;
kk--;
}
}
return true;
}
复杂度:
每组数据 \(O(n\log n)\),空间复杂度 \(O(n)\)。
常见问题及解答
-
\(Q\): \(kk\) 初始值为什么是 \(k+2\times mid+n-1\) ?
-
\(A\): 由于在进行每次操作后 \(k\) 值都会加一,那么最终值为 \(k+2\times mid+n\),最后一次操作加的一不能用,故减一
-
\(Q\): 为什么分裂时 \(kk\) 不减?
-
\(A\): 分裂不删除元素,只是把 \(1\) 个元素拆成 \(2\) 个,不消耗“删除名额”,故 \(kk\) 不变。分裂占用的时间步已含在 \(kk\) 初值的 \(2\times mid\) 中 (\(n+mid\) 次删除,\(mid\) 次分裂),循环中再减就重复了。
完整代码
#include<bits/stdc++.h>
using namespace std;
const int N=2e5+5;
int t,cnt[N],n,k,cntt[N];
inline bool check(int mid){
int kk=k+2*mid+n-1;
for(int i=1;i<=n;i++) cntt[i]=cnt[i];
int shu=n,tot=0;
while(shu){
if (cntt[shu]==0){
shu--;
continue;
}
if (shu>kk){
if (tot+1>mid) return false;
cntt[shu]--;
tot++;
cntt[(shu+1)/2]+=2;
}else{
cntt[shu]--;
kk--;
}
}
return true;
}
signed main(){
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
cin>>t;
while(t--){
cin>>n>>k;
for(int i=1;i<=n;i++){
cnt[i]=0;
}
for(int i=1;i<=n;i++){
int x;
cin>>x;
cnt[x]++;
}
int l=0,r=n*3;
while(l<r){
int mid=(l+r)>>1;
if (check(mid)){
r=mid;
}else{
l=mid+1;
}
}
cout<<n+2*r<<'\n';
}
return 0;
}
祝2026NOIP RP++

浙公网安备 33010602011771号