1. 程式人生 > 其它 >#根號分治,字首和,雙指標#CF1446D2 Frequency Problem (Hard Version)

#根號分治,字首和,雙指標#CF1446D2 Frequency Problem (Hard Version)

題目

給定一個長度為 \(n\) 的序列,問是否存在一個最長的區間使得至少存在兩個眾數。


分析

實際上 Easy Version 是用來啟發大於根號的做法的。

眾數可以說有一個性質吧,答案區間中的其中一個眾數一定是整個序列的眾數。

當然如果整個序列有多個眾數答案就是 \(n\),如果只有一種數答案就是 \(0\)

只需要考慮一種眾數的情況,先看 Easy Version(數字種類不超過一百)。

如果數字種類數足夠少,直接列舉數字種類,那麼相當於一段區間眾數和該種數字出現次數相等。

那麼將眾數視為 \(-1\),該種數字視為 \(1\),即求是否存在一段區間和為零,那直接字首和記錄最早位置即可。

但是 Hard Version 的時候不能所有數字都算一遍,

考慮枚舉出現次數 \(T\),然後用一個雙指標求出對於每一個右端點 \(r\),求出最小的 \(l\) 使得 \([l,r]\) 數字出現次數不超過 \(T\)

這樣只要有至少兩個數字出現次數為 \(T\)\([l,r]\) 就是一段符合要求的區間。

將兩種方法結合一下,\(T\) 只列舉到根號,列舉數字種類只枚舉出現次數超過根號的數字,這樣就是 \(O(n\sqrt{n})\)


程式碼

#include <cstdio>
#include <cctype>
using namespace std;
const int N=200011,bl=400;
int n,mx,se,cnt[N],a[N],c[N],las[N<<1],ans;
int iut(){
	int ans=0; char c=getchar();
	while (!isdigit(c)) c=getchar();
	while (isdigit(c)) ans=ans*10+c-48,c=getchar();
	return ans; 
}
int max(int a,int b){return a>b?a:b;}
int main(){
	n=iut();
	for (int i=1;i<=n;++i) ++cnt[a[i]=iut()];
	for (int i=1;i<=n;++i)
	if (cnt[i]>cnt[mx]) se=mx,mx=i;
	    else if (cnt[i]>cnt[se]) se=i;
	if (!se) return !printf("0");
	if (cnt[mx]==cnt[se]) return !printf("%d",n);
	for (int T=1;T<=bl;++T){
		int now=0;
		for (int i=1,j=1;i<=n;++i){
			for (;c[a[i]]==T;--c[a[j++]])
				if (c[a[j]]==T) --now;
			if (++c[a[i]]==T) ++now;
			if (now>1) ans=max(ans,i-j+1);
		}
		for (int i=0;i<=n;++i) c[i]=0;
	}
	for (int i=1;i<=n;++i)
	if (cnt[i]>bl&&i!=mx){
		for (int j=0;j<=2*n;++j) las[j]=-1; las[n]=0;
		for (int j=1,s=0;j<=n;++j){
			if (a[j]==i) ++s;
			    else if (a[j]==mx) --s;
			if (las[s+n]>=0) ans=max(ans,j-las[s+n]);
			    else las[s+n]=j;
		}
	}
	return !printf("%d",ans);
}