acm-(根号分治、序列)Codeforces Round #683 (Div. 2) F2. Frequency Problem (Hard Version)

题面
传送门
首先可以证明最终答案的子串中一定有一个元素是在整个串中出现次数最多的。为什么正确呢?我们考虑最终的答案子串的形成过程,我们可以最初取整个串作为子串,不过它可能不符合题目要求,即存在至少两个最大出现次数的元素,假设出现次数最多的元素是D,那么我们从整个串的头或尾部删除元素D,直到满足条件即可。然后容易发现当删除的是一个D,那么只有两种情况发生,如果D和其它某些元素变得相等了,那么此时就不用继续往下删了;如果D仍然大于其它元素,那么可以继续往下删。当删除的不是一个D,那么只可能是其它元素变得更小,所以还需要继续往下删。

有了这个结论后,我们先考虑Easy版本中的做法,因为最终答案子串中次数出现最多的元素至少有一个是D,那么考虑枚举另外一个出现次数最多的元素d,假设D为1,d为-1,其他元素为0,那么遍历数组的过程中我们用变量 c u r cur cur记录当前的前缀和,并用 l a s t [ c u r ] last[cur] last[cur]记录当前缀和第一次为 c u r cur cur时的位置,于是从当前位置出发向前延伸最长的满足 D D D d d d相等条件的子串的首位置一定出现在 l a s t [ c u r ] last[cur] last[cur],更新 a n s = m a x ( a n s , i − l a s t [ c u r ] ) ans=max(ans,i-last[cur]) ans=max(ans,ilast[cur])即可,不过由于 c u r cur cur可能是负数,我们需要让它先加 n n n才行。这里可能会有个问题,那就是 l a s t [ c u r ] + 1 ∼ i last[cur]+1\sim i last[cur]+1i这一段中的其他元素个数是可能超过D的个数的。但其实不用担心,由于D跟每个元素都会枚举一遍,因此所有的可能都会被枚举到,也就是最大值一定会被枚举到。

不过在Hard版本中,我们不能直接像上述方式一样做,考虑根号分治,对于 c n t [ x ] ≥ n cnt[x]\ge \sqrt n cnt[x]n 的元素 x x x我们可以像上述方式一样做,由于这样的元素最多不超过 n \sqrt n n 个,因此复杂度为 n n n\sqrt n nn 。而对于 c n t [ x ] < n cnt[x]< \sqrt n cnt[x]<n 的元素我们考虑直接枚举数量,也就是枚举出现次数最多元素数量从 1 ∼ n 1\sim \sqrt n 1n ,然后去 c h e c k check check即可。如何 c h e c k check check呢?假设当前枚举值是 t t t,那么我们固定右端点 r r r,对于左端点 l l l我们需要保证 [ l , r ] [l,r] [l,r]段中的最大出现次数元素个数不能超过 t t t,这很好维护,左端点可以根据需要往右边移动即可。

int n,a[maxn],cnt[maxn],mx=0,last[maxn*2],ans=0; 
void solve1(int x){
	FOR(i,1,n+1)last[n-i]=last[n+i]=-1;
	last[n]=0;
	int cur=0;
	FOR(i,1,n+1){
		if(a[i]==x)cur++;
		else if(a[i]==mx)cur--;
		if(last[n+cur]==-1)last[n+cur]=i;
		else ans=max(ans,i-last[n+cur]);
	}
}
void solve2(int x){
	FOR(i,1,n+1)cnt[i]=0;
	int l=1,equal=0;
	cnt[a[l]]++;if(cnt[a[l]]==x)equal++;
	FOR(i,2,n+1){
		cnt[a[i]]++;
		if(cnt[a[i]]==x)equal++;
		else if(cnt[a[i]]>x){
			if(cnt[a[i]]==x+1)equal--;
			while(cnt[a[i]]>x){
				cnt[a[l]]--;
				if(cnt[a[l]]==x)equal++;
				else if(cnt[a[l]]==x-1)equal--;
				l++;
			}
		}
		if(equal>=2)ans=max(ans,i-l+1);
	} 
}
int main(){
	n=rd();
	FOR(i,1,n+1)cnt[a[i]=rd()]++,mx=cnt[mx]<cnt[a[i]]?a[i]:mx;
	int limt=sqrt(n);
	FOR(i,1,n+1)if(cnt[i]>=limt && i!=mx)solve1(i);
	FOR(i,1,limt)solve2(i);
	wrn(ans);
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值