CODEFORCES ROUND #406 (DIV. 2) E. TILL I COLLAPSE

在这里插入图片描述

  • 考虑暴力怎么做?对于每一个 k k ,贪心地双指针扫一遍。如果加入下一个点就超过 k k 个,那就结束当前段,并且将 l l 指针移到当前位置的下一位。
  • 考虑如何优化寻找的过程。整体二分可以在log n的时间复杂度找到区间第k大值在哪里。假如我们确定了某一个右端点,那么是不是可以快速的跳若干个点,并且保证这个区间是满足不超过 k k 个的最长区间。因此我们用主席树做这道题。
  • 处理和HH项链那道题是一样的,钦定每一个节点作为右端点,权值线段树储存此时本质不同的数的信息。我们从 n n 开始向前跳,每次跳到所能跳到的最远点。具体能跳到哪儿,这就需要借助求第 K K 大值了。
  • 因为总共跳的次数的式子可以写成调和级数的形式,所以总共跳了不超过N*log N次,每次复杂度是 l o g N log N ,所以总复杂度为 O ( N l o g 2 N ) O(N*log^2N)
  • 注意,查询第 K K 大是从左到右,但是我们希望从右到左,一个小 t h i c k thick 是用区间 s u m k 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;
}

猜你喜欢

转载自blog.csdn.net/qq_39759315/article/details/88974718