【ZJOI2022】众数

一个显然的刻画是如果确定了选择的区间,那么答案就是区间内的众数出现次数加上区间外的众数出现次数。

对于与某个数出现次数有关的数的问题通常根号分治。称出现超过 n 的数为“大数”,否则为“小数”。称区间内为“内层”,区间外为“外层”,不妨讨论内层众数和外层众数。

若内层众数或者外层众数有一个是大数。枚举该大数,先考虑该大数在外层。那么此时的复杂度允许我们枚举内层的众数,设枚举到的大数是 x,出现次数为 w1,内层众数是 y,出现次数为 w2。将 x 出现的地方权值设为 1,将 y 出现的地方权值设为 1,那么求出最大子段和 s,此时的答案就是 w1+s

这个最大子段和可以直接 dp,单次和 y 相关,因此该部分的时间复杂度为 O(nn)

大数在内层的情况一样。考虑内外层均为小数的情况,看上去并不容易做,尝试将此时问题的约束变紧一点,若枚举外层小数 x,则此时的区间数量为 O(wx2) 个(必然可以调整到 al1=xar+1=x)。简单分析一下得到上限 O(nn),因此我们只需要对这 O(nn) 个区间求众数及出现次数,不难规约到一下问题:

对于 O(nn) 个区间,均摊或不均摊地 O(1) 求出这些区间的众数和 n 的较小值。

显然不均摊很难做。考虑均摊,利用双指针(或许是一堆指针)。扫描右端点,维护 pi,j 代表对于此时右端点 i,最大的一个左端点使得 [pi,j,i] 的众数出现次数 j

p 显然有单调性,同时 p 的第二维只有 O(n) 大小。先来考虑扫描 i 的时候如何维护 pi。显然暴力移动 pi1 即可,有很多方法预处理,不作赘述。

此时对于一个要求众数的区间 [l,r],在 pl 上二分能得到答案,这里的复杂度多一个 log,不妨继续双指针,对于一个固定的 l,利用双指针来求答案,复杂度为 O(nn)。这里要求 r 有序,可以简单做到。

总时间复杂度 O(nn)。当然上面写的主要是求出现次数,求到底有哪些数并不难维护。

#include<bits/stdc++.h>
// #define int long long
#define fi first
#define se second
#define pii std::pair<int,int>
#define vint std::vector<int>
#define vpair std::vector<pii>
#define rev(a) std::reverse(a.begin(),a.end())
#define debug(...) fprintf(stderr,##__VA_ARGS__)
template<typename T>
void read(T &x){
x=0;
int f=1;
char c=getchar();
while(c<'0'||c>'9'){
if(c=='-') f=-1;
c=getchar();
}
while(c>='0'&&c<='9') x=x*10+(int)(c-'0'),c=getchar();
x*=f;
}
std::stack<char>st;
template<typename T>
void print(T x){
if(x==0) putchar('0');
if(x<0) putchar('-'),x=-x;
while(st.size()) st.pop();
while(x) st.push((char)('0'+x%10)),x/=10;
while(st.size()) putchar(st.top()),st.pop();
}
template<typename T>
void printsp(T x){
print(x),putchar(' ');
}
template<typename T>
void println(T x){
print(x),putchar('\n');
}
template<typename T,typename I>
bool chkmin(T &a,I b){
if(a>b) return a=b,1;
return 0;
}
template<typename T,typename I>
bool chkmax(T &a,I b){
if(a<b) return a=b,1;
return 0;
}
template<typename T,typename I>
void addedge(std::vector<I>*vec,T u,T v){
vec[u].push_back(v);
}
template<typename T,typename I,typename K>
void addedge(std::vector<K>*vec,T u,T v,I w){
vec[u].push_back({v,w});
}
template<typename T,typename I>
void addd(std::vector<I>*vec,T u,T v){
addedge(vec,u,v),addedge(vec,v,u);
}
template<typename T,typename I,typename K>
void addd(std::vector<K>*vec,T u,T v,I w){
addedge(vec,u,v,w),addedge(vec,v,u,w);
}
bool Mbe;
const int inf=1e9+1,MOD1=998244353,MOD2=1e9+7;
const int maxn=2e5+1,SQRT=210,B=200;
int a[maxn],T,n,b[maxn],f[maxn],ans,qzh[maxn],c[maxn],p[SQRT][maxn],t[maxn],s[maxn],zs[maxn],tot[maxn],bl[maxn],br[maxn],okk[maxn],ys[maxn];
vint vec,vecs,ql[maxn],qr[maxn],posvec;
vint h[maxn];
bool Men;
signed main(){
// freopen("data.in","r",stdin),freopen("sol.out","w",stdout);
debug("%.6lfMB\n",(&Mbe-&Men)/1048576.0);
read(T);
// debug("T=%d\n",T);
while(T--){
int tim=1;
vec.clear(),vecs.clear(),posvec.clear(),ans=-inf;
read(n);
for(int i=1;i<=n;i++) read(a[i]),b[i]=a[i],h[i].clear(),qr[i].clear(),ql[i].clear(),tot[i]=0,okk[i]=0;
for(int j=1;j<=B;j++)
for(int i=1;i<=n;i++) p[j][i]=0;
std::sort(b+1,b+n+1);
for(int i=1;i<=n;i++) c[i]=std::lower_bound(b+1,b+n+1,a[i])-b,h[c[i]].push_back(i),ys[c[i]]=i;//h[c[i]] 存的是 c[i] 出现的位置
int las=0;
// debug("ok\n");
for(int i=1;i<=n;i++)
if(b[i]==b[las]) continue;
else{
if(i-las>=B&&las!=0) vec.push_back(b[las]),posvec.push_back(las);
else vecs.push_back(las);
las=i;
}
if(n+1-las>=B) vec.push_back(b[las]),posvec.push_back(las);
else vecs.push_back(las);
//存在大数
for(int i=1;i<=n;i++) f[i]=0;
int tnc=-1,tott=0,w1,w2,ff,ww,fff,ww2;
for(int i:vec){
tnc++;
int c;
//枚举此时外层的大数是 i
w1=0,w2=0;//出现次数 || 最大子段和
int sum=w1;//最后的答案就是 w1 + 最大子段和
for(int j=1;j<=n;j++){
qzh[j]=qzh[j-1];
if(a[j]==i) w1++,qzh[j]++;
}
las=0;
for(int j=1;j<=n;j++){
if(b[j]==b[j-1]) continue;
if(b[j]==i) continue;
las=j;
//枚举了内层的数是 b[j] -> 从 h[j] 中获得信息 -> 该数所有出现的位置为 h[j] 中的数
w2=0;
ff=0,las=0,ww=0,fff=0,ww2=0;
for(int k:h[j]){
tott++;
//做最大子段和的 dp f[i]=max(f[i],0)+a[i] 放在这里的话我们在枚举关键点进行转移
c=qzh[k]-qzh[las];
ff=std::max(1,ff-c+1);
chkmax(w2,ff);
ww2++;
chkmax(ww,fff+c);
fff=fff+c-1;
chkmax(fff,c-1);
chkmax(ww,fff),chkmax(ww,c);
las=k;
}
// debug("w1=%d\n",w1);
if(chkmax(ans,w1+w2)) tim++;
if(ans==w1+w2) okk[ys[posvec[tnc]]]=tim;
c=qzh[n]-qzh[las];
chkmax(ww,c);
fff=fff+c;
chkmax(ww,fff);
if(chkmax(ans,ww2+ww)) tim++;
if(ans==ww2+ww) okk[ys[j]]=tim;
}
if(chkmax(ans,w1)) tim++;
if(w1==ans) okk[ys[posvec[tnc]]]=tim;
}
//全是小数的情况
for(int j=1;j<=B;j++){
for(int i=1;i<=n;i++) s[i]=0;
for(int i=1;i<=n;i++){
s[c[i]]++;
if(s[c[i]]==j){
p[j][1]=i;
break;
}
}
if(!p[j][1]){
p[j][1]=n+1;
}
for(int i=2;i<=n;i++){
//求出 p[i][j]
s[c[i-1]]--;
p[j][i]=p[j][i-1];
if(s[c[i-1]]+1==j){
bool fl=0;
while(p[j][i]<n){
p[j][i]++;
s[c[p[j][i]]]++;
if(s[c[p[j][i]]]==j){
fl=1;
break;
}
}
if(!fl){
p[j][i]=n+1;
}
}else p[j][i]=p[j][i-1];
}
}
//能找出若干询问
for(int i:vecs){
// debug("i=%lld\n",i);
vint cl,cr;
cl.push_back(1),cr.push_back(1);
for(int j:h[i]){
if(j<n-1) cl.push_back(j+1);
if(j>2) cr.push_back(j-1);
}
cl.push_back(n),cr.push_back(n);
rev(cr);
for(int l:cl)
for(int r:cr){
if(l>r) break;
//[l,r]
qr[r].push_back(l);
}
tot[i]=h[i].size();
int pos=1;
for(int j:h[i]) bl[j]=pos,pos++;
pos=1;
rev(h[i]);
for(int j:h[i]) br[j]=pos,pos++;
}
for(int i=1;i<=n;i++)
for(int j:qr[i]) ql[j].push_back(i);
//提前处理 i=1 避免空间问题
int z=0;
for(int i=0;i<B;i++){
while(z<ql[1].size()&&ql[1][z]<p[i+1][1]){
//[1,ql[1][z]]
if(ql[1][z]==n) break;
int sum=i;
sum+=br[ql[1][z]+1];
if(chkmax(ans,sum)) tim++;
if(ans==sum) okk[ql[1][z]+1]=tim;
z++;
}
}
// debug("ok\n");
for(int i=2;i<=n;i++){
// debug("i=%lld\n",i);
z=0;
for(int j=0;j<B;j++)
while(z<ql[i].size()&&ql[i][z]<p[j+1][i]){
//[i,ql[i][z]] 答案为 j
int sum=j+bl[i-1];
if(ql[i][z]!=n) sum+=br[ql[i][z]+1];
if(chkmax(ans,sum)) tim++;
if(ans==sum) okk[i-1]=tim;
z++;
}
}
println(ans);
vint sol;
for(int i=1;i<=n;i++) if(okk[i]==tim) sol.push_back(a[i]);
std::sort(sol.begin(),sol.end());
auto zzz=std::unique(sol.begin(),sol.end());
for(auto i=sol.begin();zzz!=i;i++) println(*i);
}
debug("%.6lfms\n",1e3*clock()/CLOCKS_PER_SEC);
}
/*
1
4
1 3 2 2
1
16
13 16 14 16 9 14 16 10 10 15 4 8 6 15 10 9
1
7
5 7 3 5 4 4 4
1
14
11 14 12 10 11 10 7 6 1 4 11 3 11 4
1
18
6 11 2 2 11 6 14 2 3 5 3 8 13 2 11 12 10 10
*/
posted @   BYR_KKK  阅读(27)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】
点击右上角即可分享
微信分享提示