- 考虑暴力怎么做?对于每一个
k,贪心地双指针扫一遍。如果加入下一个点就超过
k个,那就结束当前段,并且将
l指针移到当前位置的下一位。
- 考虑如何优化寻找的过程。整体二分可以在log n的时间复杂度找到区间第k大值在哪里。假如我们确定了某一个右端点,那么是不是可以快速的跳若干个点,并且保证这个区间是满足不超过
k个的最长区间。因此我们用主席树做这道题。
- 处理和HH项链那道题是一样的,钦定每一个节点作为右端点,权值线段树储存此时本质不同的数的信息。我们从
n开始向前跳,每次跳到所能跳到的最远点。具体能跳到哪儿,这就需要借助求第
K大值了。
- 因为总共跳的次数的式子可以写成调和级数的形式,所以总共跳了不超过N*log N次,每次复杂度是
logN,所以总复杂度为
O(N∗log2N)。
- 注意,查询第
K大是从左到右,但是我们希望从右到左,一个小
thick是用区间
sum−k就好了。(思想僵化哎)
#include<bits/stdc++.h>
using namespace std;
const int N=1e6+10;
int n,tot,rt[N],a[N],lef[N],sum[N*20],ls[N*20],rs[N*20];
void update(int &o,int pre,int l,int r,int x,int val){
o=++tot;
sum[o]=sum[pre]+val;
ls[o]=ls[pre];
rs[o]=rs[pre];
if(l==r) return ;
int mid=l+r>>1;
if(x<=mid) update(ls[o],ls[pre],l,mid,x,val);
else update(rs[o],rs[pre],mid+1,r,x,val);
}
int query(int o,int l,int r,int k){
if(l==r) return l;
int cnt=sum[ls[o]],mid=l+r>>1;
if(cnt>=k) return query(ls[o],l,mid,k);
else return query(rs[o],mid+1,r,k-cnt);
}
int kth(int r,int k){
int siz=sum[rt[r]];
if(!siz) return 1;
k=siz-k;if(k<=0) return 1;
return query(rt[r],1,n,k)+1;
}
int main(){
scanf("%d",&n);
for(int i=1;i<=n;++i) scanf("%d",&a[i]);
for(int i=1;i<=n;++i){
if(lef[a[i]]){
update(rt[i],rt[i-1],1,n,lef[a[i]],-1);
update(rt[i],rt[i],1,n,i,1);
}else update(rt[i],rt[i-1],1,n,i,1);
lef[a[i]]=i;
}
for(int k=1;k<=n;++k){
int ans=0;int l,r=n;
while(r){
ans++;
l=kth(r,k);
r=l-1;
}
printf("%d\n",ans);
}
return 0;
}