众数 题解

· · 题解

来源:浙江省选 2022

编号:P8330

tag:根号分治

难度:紫

没怎么理解题解区大佬的题解,若有重复请见谅。

考虑最终答案的形态。x 为最终众数的答案必然为 x\dots x,x-k\dots x-k,x\dots x。前后两段 x 不一定要同时出现,但是至少要出现一段(不然就变成一些数变成左右段序列没有出现过的数就不会更优,容易证明一定有比完全更优的解)。

wjz 学长说的很有道理,出现次数 一般和 根号分治 挂钩。所以我们考虑对用一种颜色出现次数进行根号分治。设阈值为 B

cnt_p\gt B 时,把这些颜色单独拎出来做。具体地,处理颜色 p 的前后缀出现次数,然后对于每一种其他颜色 i,处理出类似 i\dots i,p\dots p,i\dots ip\dots p,i\dots i,p\dots p 形态的的答案并分别对 ans_i,ans_p 进行贡献。这很好处理,对于 i\dots i,p\dots p,i\dots i,处理出每个 i 作为左段右端点和右端左端点对答案产生的影响 sum_i-sum_p。处理前后缀 \max 很容易做到线性。故这部分总时间复杂度为 \Theta(\frac{n^2}{B})

cnt_p\le B 时,考虑到此时只剩左右段和中间段出现次数均 \le B 的情况。固定中间段的右端点发现最优中间段仅有 B 种,即其他颜色分别出现 1\sim B 次。接下来只要考虑如何统计颜色 p 作为左右段颜色的贡献和颜色 p 作为中间段颜色的贡献。右端点扫描线,f_i 表示当前节点作为右端点,其他颜色作为中间段出现 i 次的最大左端点,每遍历到一个颜色就对这个大小为 B 的数组进行遍历更新。考虑 p 作为左右段颜色产生贡献,只需枚举 p 位置集合元素作为左端点时最大的 i 满足 f_i\ge L。由于 f_i 单调递减,所以在倒序遍历颜色 p 集合的同时放一个指针在 f 数组上跑,类似双指针的思想。可以发现每个元素最多贡献 \Theta(\max(B,cnt_p))。故总时间复杂度为 \Theta(nB)

注意有可能区间不恰好包含在两个同颜色元素之间,所以单独前后缀处理一下即可。

B\sqrt{n} 有最优复杂度。

```cpp #include<bits/stdc++.h> using namespace std; const int N=2e5+5; const int K=455; const int inf=1e9; int n,a[N],num,B; int cnt[N]; int m,tmp[N]; vector<int>lnk[N]; int ans[N]; void init(){ scanf("%d",&n); for(int i=1;i<=n;i++){ lnk[i].clear(); ans[i]=0; cnt[i]=0; } m=n; for(int i=1;i<=n;i++){ scanf("%d",&a[i]); tmp[i]=a[i]; } sort(tmp+1,tmp+m+1); m=unique(tmp+1,tmp+m+1)-tmp-1; for(int i=1;i<=n;i++){ a[i]=lower_bound(tmp+1,tmp+m+1,a[i])-tmp; lnk[a[i]].push_back(i); ++cnt[a[i]]; } B=sqrt(n*1.0); } int f[N],g[N],F[N],G[N]; void big(int p){ for(int i=0;i<=n+1;i++){ f[i]=g[i]=0; } for(auto x:lnk[p]){ ++f[x]; ++g[x]; } for(int i=1;i<=n;i++){ f[i]=f[i]+f[i-1]; } for(int i=n;i;i--){ g[i]=g[i]+g[i+1]; } int val=cnt[p]; for(int i=1;i<=m;i++){ if(i==p){ continue; } int k=cnt[i]; int res=-inf; F[0]=G[k+1]=0; for(int j=0;j<k;j++){ F[j+1]=max(F[j],(j+1)-f[lnk[i][j]]); res=max(res,F[j+1]+val); } for(int j=k-1;~j;j--){ G[j+1]=max(G[j+2],(k-(j+1)+1)-g[lnk[i][j]]); res=max(res,G[j+1]+val); } for(int j=1;j<=k;j++){ res=max(res,F[j]+G[j]+val); } ans[i]=max(ans[i],res); res=-inf; F[0]=G[k+1]=0; for(int j=0;j<k;j++){ F[j+1]=max(F[j],f[lnk[i][j]]-j); res=max(res,F[j+1]+k); } for(int j=k-1;~j;j--){ G[j+1]=max(G[j+2],g[lnk[i][j]]-(k-(j+1))); res=max(res,G[j+1]+k); } for(int j=1;j<=k;j++){ res=max(res,F[j]+G[j]+k); } ans[p]=max(ans[p],res); } } int h[K]; void small(){ for(int i=1;i<=B;i++){ h[i]=0; } for(int i=1;i<=n;i++){ if(cnt[a[i]]>B){ continue; } int k=cnt[a[i]],sz=k,p=0; for(int j=k-1;~j;j--){ if(lnk[a[i]][j]>=i){ continue; } while(p+1<=B&&h[p+1]>lnk[a[i]][j]){ ++p; } ans[a[i]]=max(ans[a[i]],sz+p); --sz; } sz=0; for(int j=k-1;~j;j--){ if(lnk[a[i]][j]>i){ continue; } ++sz; h[sz]=max(h[sz],lnk[a[i]][j]); } } } int buc[N],id[N],pre[N],suf[N]; void spc(){ for(int i=1;i<=n;i++){ buc[i]=0; } for(int i=1;i<=n;i++){ pre[i]=id[i]=++buc[a[i]]; suf[i]=cnt[a[i]]-buc[a[i]]+1; } pre[0]=suf[n+1]=0; for(int i=1;i<=n;i++){ pre[i]=max(pre[i],pre[i-1]); } for(int i=n;i;i--){ suf[i]=max(suf[i],suf[i+1]); } for(int i=1;i<=n;i++){ ans[a[i]]=max(ans[a[i]],id[i]+suf[i+1]); ans[a[i]]=max(ans[a[i]],(cnt[a[i]]-id[i]+1)+pre[i-1]); } } void print(){ int val=0; for(int i=1;i<=m;i++){ val=max(val,ans[i]); } printf("%d\n",val); for(int i=1;i<=m;i++){ if(ans[i]==val){ printf("%d\n",tmp[i]); } } } void solve(){ init(); for(int i=1;i<=m;i++){ if(cnt[i]>B){ big(i); } } small(); spc(); print(); } int main(){ int T; scanf("%d",&T); while(T--){ solve(); } return 0; } ``` 若有疑问或错误请指出,虚心接受您的意见。