CF1784C Monsters (hard version) 题解
分析
沿用和 easy version 一样的思路,即操作 1 会将原序列减小至连续递增状态再进行操作 2。
以下的操作均指操作 1。
从小到大考虑序列中的元素,可以发现对于前
#include<bits/stdc++.h>
using namespace std;
using ll = long long;
const int N = 200010;
int n;
int a[N];
void solve(){
cin>>n;
for (int i=1;i<=n;++i) cin>>a[i];
sort(a+1,a+1+n);
ll ans=0,len=0;
for (int i=1;i<=n;++i){
len++;
if (a[i]>=len) ans+=a[i]-len;
else len--;
}
cout<<ans<<endl;
}
int main(){
int t;cin>>t;
while(t--) solve();
return 0;
}
回到 hard version,我们记录当前真正被进行操作的数字个数
新加入一个数
若此时
否则,找到最小的使得
可以发现
上述过程涉及区间加和查询最小值,可以维护一颗线段树,查询
每次加入一个数后修改答案即可。
时间复杂度
代码如下:
#include<bits/stdc++.h>
using namespace std;
using ll = long long;
const int N = 200010;
int n;
int a[N];
struct node{
int mn,tag;
#define ls p<<1
#define rs p<<1|1
#define mn(p) t[p].mn
#define tag(p) t[p].tag
}t[N<<2];
void pushdown(int p){
if (tag(p)!=0){
mn(ls)+=tag(p); mn(rs)+=tag(p);
tag(ls)+=tag(p); tag(rs)+=tag(p);
tag(p)=0;
}
}
void pushup(int p){
mn(p)=min(mn(ls),mn(rs));
}
void build(int p,int l,int r){
tag(p)=0;
if (l==r) {
mn(p)=l;
return;
}
int mid=(l+r)>>1;
build(ls,l,mid); build(rs,mid+1,r);
pushup(p);
}
void update(int p,int l,int r,int pl,int pr,int d){
if (l<=pl && pr<=r){
mn(p)+=d; tag(p)+=d;
return;
}
pushdown(p);
int mid=(pl+pr)>>1;
if (mid>=l) update(ls,l,r,pl,mid,d);
if (mid+1<=r) update(rs,l,r,mid+1,pr,d);
pushup(p);
}
int find(int p,int l,int r){
if (mn(p)>=0) return 0;
if (l==r) return l;
pushdown(p);
int mid=(l+r)>>1;
if (mn(ls)<0) return find(ls,l,mid);
else return find(rs,mid+1,r);
}
void solve(){
cin>>n;
for (int i=1;i<=n;++i) cin>>a[i];
ll ans=0,len=0;
build(1,1,n);
for (int i=1;i<=n;++i){
update(1,a[i],n,1,n,-1);
int pos=find(1,1,n);
if (pos){//ai对之后的答案有更新
ans+=a[i]-pos;
update(1,pos,n,1,n,1);
}
else {
len++;
ans+=a[i]-len;
}
printf("%lld ",ans);
}
puts("");
}
int main(){
int T;cin>>T;
while(T--) solve();
return 0;
}