CF1870G MEXanization[Hard+]
前言
题目传送门
NOIP 模拟赛的 T2 放这题,真是太难泵了。
题意
下文集合代指可重集。
给你一个集合,每次可以将集合内任意元素取出并将他们的 mex 加入集合,集合最后剩下的元素为贡献。
现在我们需要求给定数组的每个前缀组成的集合的最大贡献。
解题思路
首先考虑如何构造方案得到最大贡献。
因为除了长度为 \(1\) 的前缀外,其他集合最后得到贡献肯定是集合内剩下元素一起取到的 mex,设贡献为 \(x\),则集合内一定要 \(0\) 到 \(mex-1\) 的数至少一个。以此类推,我们希望用集合内的数依次凑出 \(mex-1,mex-2,...,0\) 这些数。
我们想要凑出一个数 \(num\),则需要消耗 \(0,1,...,num-1\) 各一个。于是依次凑 \(mex-1,mex-2,...,0\),手玩一下发现这类似于一个前缀减操作。设数字 \(i\) 有 \(h_i\) 个,我们从 \(x-1\) 往 \(0\) 扫,一开始先给 \([0,x-1]\) 的 \(h_i\) 进行 \(-1\),之后扫到一个 \(h_i\),若 \(h_i \leq 0\),则说明需要前面的数额外凑出 \(-h_i\) 个 \(i\),相当于再对 \([0,i-1]\) 减掉 \((-h_i)\)。最后判断一下 \(h_0\) 非负即可。
但是发现这样不是最优的,因为第一个样例就会爆炸,最后一个数是 \(5\),但是我们会输出 \(4\)。
对于任意一个数,都可以与自己进行 mex 变成 \(0\)。我们发现在扫的过程中可能会出现 \(h_i\) 比较富足的情况,这时候就可以反向变成 \(0\) 去补 \(h_0\),对于 \(i \geq x\) 直接当成 \(0\) 即可。
于是就知道了如何得到最大贡献。
抛开第一个数组成的集合,往集合加数一定不会使答案更劣,而集合答案肯定不会超过 \(n\),而判断一次需要 \(O(n)\) 的时间复杂度,所以时间复杂度做到了 \(O(n^2)\)。
check 大概长这样:
//buc就是题解所说的h
bool check(int x){
for(int i=0;i<x;i++) buc[i]=sum[i];
for(int i=x;i<=n;i++) buc[0]+=sum[i];
int nd=1;
for(int i=x-1;i>=1;i--){
int tmp=max(nd-buc[i],0);
int ex=max(buc[i]-nd,0);
buc[0]+=ex,nd+=tmp;
if(nd>n) return false; //防溢出
}
return buc[0]>=nd;
}
现在考虑优化。
发现其实上面的操作类似于用一条线 \(nd=1\) 去扫 \(h_i\),遇到 \(h_i \leq nd\),\(nd\) 就会抬高 \(nd-h_i\),然后有一个可以填补 \(h_0\) 的数 \(ex\),遇到 \(h_i \geq nd\) 就可以另 \(ex\) 加 \(h_i - nd\)。最后判断 \(h_0 + ex\) 是否 \(\geq nd\) 即可。
其实如果有感觉可以猜到,\(nd\) 被抬高的次数并不多(否则会爆出 \(n\))。考虑证明一下,每次 \(h_i < nd\) 时,至少会让总需求增加 \(i-1\) 个(这里的总需求不是 \(nd\),而是满足当前检查的 \(x\) 可以被凑出的所需的最少集合内的元素个数),而 \(\sum h_i = n\),我们需要总需求必须满足 \(\leq n\),而每次 \(nd\) 被抬高的的 \(i\) 互不相同,所以 \(i\) 的个数不会超过 \(O(\sqrt n)\) 个。
然后开个线段树维护 \(h_i\) 的最小值,每次线段树二分找靠右的 \(h_i < nd\) 即可。这样我们可以做到 \(O(n\sqrt n \log n)\)。
个数 \(O(\sqrt n)\) 是逃不掉的,考虑能否直接一次性搞到所有位置。有一个比较明显的优化,对于线段树上一个区间 \([l,r]\),若 \(\min h_i \geq nd\),则可以直接另 \(ex\) 加上 \(\sum h_i - (r-l+1) \cdot nd\)。感觉能跑很快。
考虑证明时间复杂度。对于线段树上一段区间考虑,假设这层区间 \([l,r]\) 长度为 \(2^k\),且是从左往右第 \(j\) 个区间,则当前区间内若出现 \(h_i \leq nd\) 的点,会另总需求增加至少 \((j-1)2^k\)。跟刚才的时间复杂度证明同理,因为一层内的 \(j\) 各不相同,所以对于同一层这样的区间个数不会超过 $\sqrt \frac{n}{2^x} $,而 \(\sum_{x=0}^{\log n} \frac{n}{2^x} = \sqrt n\),时间复杂度是 \(O(n \sqrt n)\)。
其实上面说了那么多感觉感觉的,这道题还是挺反直觉的,很难想到做法(光是观察出至多 \(\sqrt n\) 个不同的位置都挺难的了)。
Code
#include<bits/stdc++.h>
#define ll long long
#define ull unsigned long long
#define uint unsigned int
#define i128 __int128
#define ld long double
#define fir first
#define sec second
#define pii pair<int,int>
#define pll pair<ll,ll>
#define ls(x) (x<<1)
#define rs(x) (x<<1|1)
#define lowbit(x) (x&-x)
using namespace std;
namespace FastIO{ //依旧超长前摇
const int Size=1<<21;
char ibuf[Size],obuf[Size],*p1=ibuf,*p2=ibuf,*p3=obuf;
#define getchar() (p1==p2&&(p2=(p1=ibuf)+fread(ibuf,1,Size,stdin),p1==p2)?EOF:*p1++)
#define putchar(x) (p3-obuf<Size?(*p3++=(x)):(fwrite(obuf,1,p3-obuf,stdout),p3=obuf,*p3++=(x)))
inline void flush(){if(p3>obuf) fwrite(obuf,1,p3-obuf,stdout),p3=obuf;}
template<class T>
T read(T&x){
x=0;bool f=false;char ch=getchar();
while(!isdigit(ch)) f|=!(ch^'-'),ch=getchar();
while(isdigit(ch)) x=(x<<1)+(x<<3)+(ch&0xF),ch=getchar();
x=(f?-x:x);return x;
}template<class T>
int reads(T*s){
char ch=getchar();int len=0;
while(ch==' '||ch=='\n'||ch=='\r') ch=getchar();
while(ch!=' '&&ch!='\n'&&ch!=EOF&&ch!='\r') s[len++]=ch,ch=getchar();
s[len]='\0';return len;
}template<class T>
T readd(T&x){
x=0;bool f=false;char ch=getchar();
while(!isdigit(ch)) f|=!(ch^'-'),ch=getchar();
while(isdigit(ch)) x=x*10+(ch&0xF),ch=getchar();
if(ch=='.'){ch=getchar();T d=1;while(isdigit(ch)) d*=0.1,x+=d*(ch&0xF),ch=getchar();}
x=(f?-x:x);return x;
}template<class T>
void write(T x,char ch=' '){
if(x<0) putchar('-'),x=-x;
char tmp[41];int cnt=0;
while(x>9) tmp[cnt++]=x%10+'0',x/=10;tmp[cnt++]=x+'0';
while(cnt) putchar(tmp[--cnt]);putchar(ch);
}template<class T>
void writes(T x,int l=0,int r=-1){
if(~r){for(int i=l;i<=r;i++) putchar(x[i]);}
else{for(int i=l;x[i];i++) putchar(x[i]);}
}template<class T>
void writed(T x,int p=6,char ch=' '){
if(x<0) putchar('-'),x=-x;
T d=0.5;for(int i=0;i<p;i++) d*=0.1;x+=d;
i128 g=(i128)(x);p?write(g,'.'):write(g,ch);
if(p){T f=x-g;for(int i=0,d;i<p;i++) f*=10,d=(int)(f),putchar(d+'0'),f-=d;putchar(ch);}
}
}
using namespace FastIO;
const int N=4e5+10;
int a[N],sum[N],buc[N],n,T;
bool flag;
struct segment_tree{
int minn[N<<2],sum[N<<2];
inline void pushup(int x){minn[x]=min(minn[ls(x)],minn[rs(x)]),sum[x]=sum[ls(x)]+sum[rs(x)];}
void build(int x,int l,int r){
sum[x]=minn[x]=0;
if(l==r){sum[x]=minn[x]=0;return;}
int mid=(l+r)>>1;
build(ls(x),l,mid),build(rs(x),mid+1,r);
pushup(x);
}
void update(int x,int l,int r,int pos,int val){
if(l==r){sum[x]+=val,minn[x]+=val;return;}
int mid=(l+r)>>1;
if(pos<=mid) update(ls(x),l,mid,pos,val);
else update(rs(x),mid+1,r,pos,val);
pushup(x);
}
void query(int x,int l,int r,int lim,ll&nd,ll&ex){
if(flag) return;
if(minn[x]>=nd&&r<=lim){ex+=1ll*sum[x]-nd*(r-l+1);return;}
if(l==r){
nd+=nd-minn[x];
if(nd>n) flag=true;
return;
}
int mid=(l+r)>>1;
if(mid<lim) query(rs(x),mid+1,r,lim,nd,ex);
query(ls(x),l,mid,lim,nd,ex);
}
}Tree;
bool check(int x){
flag=false;
ll nd=1,ex=0;
Tree.query(1,1,n,x-1,nd,ex);
if(flag) return false;
if(ex+buc[0]>=nd){
buc[0]-=sum[x],buc[x]+=sum[x];
Tree.update(1,1,n,x,buc[x]);
return true;
}
else return false;
}
void work(){
read(n);
for(int i=1;i<=n;i++) read(a[i]);
Tree.build(1,1,n);
write(max(a[1],1));
buc[0]++;
if(a[1]<=n) sum[a[1]]++;
else sum[0]++;
int ans=1;
for(int i=2;i<=n;i++){
if(a[i]<=n) sum[a[i]]++;
else sum[0]++;
if(a[i]>ans||!a[i]) buc[0]++;
else if(a[i]<=n) Tree.update(1,1,n,a[i],1),buc[a[i]]++;
while(ans<=n&&check(ans+1)) ans++;
write(ans);
}
putchar('\n');
for(int i=0;i<=n;i++) buc[i]=sum[i]=0;
}
int main(){
// freopen("mex.in","r",stdin);
// freopen("mex.out","w",stdout);
read(T);
while(T--) work();
flush();
// fclose(stdin);fclose(stdout);
return 0;
}

浙公网安备 33010602011771号