[Spark] 古老系列回归之—— [ZJOI2022] 众数

[Spark] 古老系列回归之——[ZJOI2022] 众数

我觉得值得评黑,至少比 [Ynoi 2012] 惊惶的 SCOI2016 难……


一般与颜色出现次数相关的问题,可以考虑根号分治。况且众数的问题可能很多还是不能 Polylog 的。


题目可以转化成求一个区间 \([l,r]\) 最大化区间内众数出现次数与区间外众数出现次数的和。

首先我们考虑设定阈值 \(B\),称出现次数不大于 \(B\) 的颜色是小颜色,否则称为大颜色,一个颜色 \(x\) 的出现次数记作 \(cnt_x\)。分类讨论进行贡献:

Part 1. 存在一层的众数是大颜色

大颜色的数量是有限的,我们可以枚举它,并接受单次时间 \(\mathcal O(n)\) 的处理,考虑枚举外层的颜色是 \(x\),内层的颜色是 \(y\),那么答案就是外层的 \(x\) 出现次数加上内层 \(y\) 的出现次数的最大值,先把 \(x\) 的出现次数计入答案,然后给数列上 \(x\) 的位置赋权值 \(-1\)\(y\) 的位置赋权值为 \(1\),那么问题就变成了求最大字段和。

而我们有一个发现,那就是最优方案中一定存在一种满足 \(a_l=y,a_r=y\),如果不是的话,向内收缩到左右端点权值都是 \(1\) 的情况显然更优,所以我们可以用 \(\mathcal O(cnt_y)\) 的复杂度来计算 \(y\)\(x\) 的可能贡献。我们 \(w_x\)

内层的众数是大颜色是类似的,但是此时的区间有可能从 1 开始或者以 n 为结尾,我们之后再特殊处理一下分成两段的即可。

Part 2. 两层的众数都是小颜色

我们直接枚举外层是小颜色 \(x\),此时我们需要找到若干区间用区间内众数的出现次数加外层 \(x\) 的出现次数去更新答案,与 Part 1 差不多,我们发现区间的左右端点的两侧一定是 \(x\)(或者在边界上,这个和 Part 1 结尾的特判可以放在一起处理,我们发现这两者的另一个端点都是对应颜色的左侧或右侧一个点,可以一起处理掉)。我们发现,这种区间的个数是 \(cnt_x^2\) 个的,假设所有颜色都是小颜色那么一个点最多和 \(\mathcal O(B)\) 个同色左端点配成一个区间,所以,区间总数量是 \(\mathcal O(nB)\) 的。

然后我们就要考虑如何做 \(\mathcal O(nB)\) 的区间众数查询……

我觉得这一坨可以单独出成题……

这一坨的处理我没看题解,感觉题解的方法需要一定注意力,比较难想到,虽然感觉这个方法也半斤八两,但是肯定还是好一些的。注意以下我们全都是只考虑小颜色。

我们先把询问挂在右端点上。然后对于每个右端点,如果它是小颜色,就处理出这种颜色在它之前的出现,这个能够 \(\mathcal O(nB)\) 地做。然后从大到小,即 \(B\)\(1\) 枚举所有可能的答案 \(k\),从左往右扫描序列,并维护一个 \(mx\) 表示若当前在 \(i\),那么左端点在 \(1\sim mx\),右端点在 \(i\sim n\) 的询问的答案都至少是 \(k\),对于一个确定的右端点,其答案关于左端点单调不增,因此是对的。这个 \(mx\) 就等于 \(1\sim i\) 中所有右端点的左边第 \(k\) 个同色位置的最大值。每个询问都只会被处理一次,一边回答询问一边更新答案即可。

Part 3. 代码

#include<bits/stdc++.h>
using namespace std;
const int N=2e5+5;
int Tsk;
int n,a[N];
struct DiscreteUtil{
    int disc[N],btot;
    void clear(){btot=0;}
    void push(int x){disc[++btot]=x;}
    void discrete(){
        sort(disc+1,disc+1+btot);
        btot=unique(disc+1,disc+1+btot)-disc-1;
    }
    int query(int x){return lower_bound(disc+1,disc+1+btot,x)-disc;}
}dc;
int cnt[N],sum[N],w[N];
const int B=450;
vector<int> pos[N];
void Work1(){
    for(int i=1;i<=dc.btot;i++){
        if(cnt[i]<=B) continue;
        for(int j=1;j<=n;j++) sum[j]=sum[j-1]+(a[j]==i);
        for(int j=1;j<=dc.btot;j++){//里面的 
            int c=0,ans=0,lst=0;
            for(int p:pos[j])
                c=max(c+1-(sum[p]-sum[lst]),1),ans=max(ans,c),lst=p;
            w[i]=max(w[i],cnt[i]+ans);
        }//Case 1
        for(int j=1;j<=dc.btot;j++){//外面的
            int c=0,ans=0,lst=0;
            for(int p:pos[j]){
                ans=max(ans,c+(sum[p]-sum[lst]));
                c=max(c+(sum[p]-sum[lst])-1,0),lst=p;
            }
            w[j]=max(w[j],cnt[j]+ans);
        }//Case 2
    }
}
vector<int> qry[N];int pmd[N],smd[N],tmp[N];
vector<int> lst[N];int scnt[N];
void Prework(){
    for(int i=0;i<=n+3;i++) tmp[i]=cnt[i]=sum[i]=w[i]=scnt[i]=pmd[i]=smd[i]=0,qry[i].clear(),lst[i].clear(),pos[i].clear();
    for(int i=1;i<=n;i++) dc.push(a[i]);
    dc.discrete();
    for(int i=1;i<=n;i++) a[i]=dc.query(a[i]);
    for(int i=1;i<=n;i++){
        pos[a[i]].push_back(i);cnt[a[i]]++;
    }
    for(int i=1;i<=n;i++){
        if(cnt[a[i]]<=B){
            if(tmp[a[i]]) lst[i].push_back(tmp[a[i]]);
            for(int j:lst[tmp[a[i]]]) lst[i].push_back(j);    
        }
        tmp[a[i]]=i;
    }
}
void Work2(){
    for(int i=0;i<=n;i++) qry[i].clear();
    for(int i=1;i<=n;i++){
        if(cnt[a[i]]>B) goto nxt;
        for(int j:lst[i])
            if(j+1<=i-1) qry[i-1].push_back(j+1);
        nxt:;
        if(lst[i].size()) scnt[i]=scnt[lst[i][0]]+1;
        else scnt[i]=1;
    }//较远的插在后面了
    for(int i=B;i>=1;i--){
        int mx=0;
        for(int j=1;j<=n;j++){
            if(cnt[a[i]]<=B){
                if(i==1) mx=max(mx,j);
                else if(lst[j].size()>=i-1) mx=max(lst[j][i-2],mx);    
            }
            while(!qry[j].empty()){
                if(qry[j].back()>mx) break;
                int k=qry[j].back();qry[j].pop_back();
                w[a[j+1]]=max(w[a[j+1]],cnt[a[j+1]]-(scnt[j+1]-scnt[k-1]-1)+i);
            }
        }
    }
}
void Work3(){//枚举外层数,预处理后缀/前缀众数 
    for(int i=0;i<=dc.btot+1;i++) tmp[i]=0;
    for(int i=1;i<=n;i++) tmp[a[i]]++,pmd[i]=max(tmp[a[i]],pmd[i-1]);
    for(int i=0;i<=dc.btot+1;i++) tmp[i]=0;
    for(int i=n;i>=1;i--) tmp[a[i]]++,smd[i]=max(tmp[a[i]],smd[i+1]);
    for(int i=1;i<=dc.btot;i++){
        int c=0;
        for(int j:pos[i]){
            w[i]=max(w[i],pmd[j-1]+cnt[i]-c);
            c++;
            w[i]=max(w[i],c+smd[j+1]);
        }
    }
}
int main(){
    ios::sync_with_stdio(false),cin.tie(nullptr);
    cin>>Tsk;
    while(Tsk--){
        dc.clear();
        cin>>n;
        for(int i=1;i<=n;i++) cin>>a[i];
        Prework(),Work1(),Work2(),Work3();
        int ans=0;
        for(int i=1;i<=n;i++) ans=max(ans,w[i]);
        cout<<ans<<"\n";
        for(int i=1;i<=dc.btot;i++) if(w[i]==ans) cout<<dc.disc[i]<<"\n";
    }
}
posted @ 2025-06-06 17:33  haozexu  阅读(24)  评论(0)    收藏  举报