#根號分治,字首和,雙指標#CF1446D2 Frequency Problem (Hard Version)
阿新 • • 發佈:2022-04-15
題目
給定一個長度為 \(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); }