牛客小白月赛117 D-众数

D.众数


原题链接

题意简述

\(\hspace{15pt}\)给定一个长度为 \(n\) 的数组 \(a_1,a_2,\dots,a_n\),现在你需要执行恰好一次如下操作:
\(\hspace{15pt}\bullet\,\)选择两个不同的位置 \(i,j\) \(\left ( 1\leqq i,j\leqq n;\ i\ne j\right )\),使得 \(a_i\) 变为 \(a_i+1\)\(a_j\) 变为 \(a_j-1\)
\(\hspace{15pt}\)现在,我们想知道,对于每一种不同的操作方式后得到的新数组(不包含不操作的情况),它们的众数都会是什么。你需要将全部可能称为众数的数字去重后,从小到大依次输出。
【名词解释】
\(\hspace{15pt}\)一个数组的众数:指的是这个数在数组中的出现次数最多,如果有多个数出现次数最多,则这些数中最大的那个数是众数,如:\(\{1,2,2,4\}\) 的众数为 \(2\)\(\{3,3,2,2,1,1\}\) 的众数为 \(3\)

解题思路1

\(n\) 的范围是\([2,1e3]\),适合$ O(n^2) $ 甚至 \(O(n^2·lgn)\)的暴力枚举,\(a_i \leq 1e6\),想到一个桶维护的基本思路。又因为要维护区间每对 \(i\)\(j\) 下的众数。我们可以贪心的每次把数字中出现次数最多的最大的数排到序列最前面或最后面,取出统计到答案数组后。注意到,这样的操作可以用一个自定义排序的 \(set\) 容器完成。

AC code1

#include<bits/stdc++.h>
using namespace std;
#define endl '\n'
typedef long long ll;
const int N=1e6+5;
struct node{
    int x,num;
    node(int A,int B) {x=A,num=B;}
    friend bool operator<(node a,node b){
        return a.num==b.num?a.x<b.x:a.num<b.num;
    }
};
set<node>st;
int cnt[N];
void add(int x){
    auto it=st.find(node(x,cnt[x]));
    cnt[x]++;
    if(it!=st.end()) st.erase(it);
    st.emplace(node(x,cnt[x]));
}
void del(int x){
    auto it=st.find(node(x,cnt[x]));
    st.erase(it);
    if(--cnt[x]>=1) st.emplace(node(x,cnt[x]));
}
int main(){
    cin.tie(0)->ios::sync_with_stdio(false);
    int n;cin>>n;
    vector<int>a(n+1);
    vector<int>ans;
    for(int i=1;i<=n;i++) {
        cin>>a[i];
        add(a[i]);
    }
    for(int i=1;i<=n;i++){
        for(int j=1;j<=n;j++){
            if(i==j) continue;
            del(a[i]);add(a[i]+1);
            del(a[j]);add(a[j]-1);
            ans.emplace_back(st.rbegin()->x);
            del(a[i]+1);add(a[i]);
            del(a[j]-1);add(a[j]);
        }
    }
    sort(ans.begin(),ans.end());
    ans.erase(unique(ans.begin(),ans.end()),ans.end());
    for(auto &x:ans) cout<<x<<' ';
    cout<<endl;
    return 0;
}

解题思路2

注意到,每次操作相当于对序列中四个数的出现次数发生了改变,\(cnt[a[i]]\)--,\(cnt[a[i]+1]\)++,\(cnt[a[j]]\)--,\(cnt[a[j]-1]\)++,那么这次改变后的众数一定是以下三种之一:1.\(a[i]+1\),2.\(a[j]-1\),3.原序列(未发生改变)的前五个最接近众数的数。
那我们就可以得到一种优化的贪心思路,维护下标数组,按照自定义排序函数按接近众数的级别对其排序

AC code2

#include<bits/stdc++.h>
using namespace std;
#define endl '\n'
typedef long long ll;
int main(){
    cin.tie(0)->ios::sync_with_stdio(false);
    int n;cin>>n;
    vector<int>a(n+1);
    int maxn=0;
    for(int i=1;i<=n;i++) cin>>a[i],maxn=max(maxn,a[i]);
    vector<int>idx(maxn+2),cnt(maxn+2);
    for(int i=1;i<=n;i++) ++cnt[a[i]];
    iota(idx.begin(),idx.end(),0);//维护下标序列,初始化为0~maxn+1,到最大可行众数值
    sort(idx.begin(),idx.end(),[&](int a,int b){
        return cnt[a]==cnt[b]?a>b:cnt[a]>cnt[b];
    });
    vector<int>ans;
    pair<int,int>w;
    auto cmp=[&](pair<int,int>cur){
        if(w.second<cur.second) w=cur;
        else if(w.second==cur.second&&w.first<cur.first) w=cur;
    };
    for(int i=1;i<=n;i++){
        for(int j=1;j<=n;j++){
            if(i==j) continue;
            --cnt[a[i]],++cnt[a[i]+1];
            --cnt[a[j]],++cnt[a[j]-1];
            w=make_pair(-1,-1);
            cmp({a[i]+1,cnt[a[i]+1]});
            cmp({a[j]-1,cnt[a[j]-1]});//增加两个数作为新的候选众数
            for(int i=0;i<5;i++) cmp({idx[i],cnt[idx[i]]});//第三种产生众数的情况
            ans.push_back(w.first);
            ++cnt[a[i]],--cnt[a[i]+1];
            ++cnt[a[j]],--cnt[a[j]-1];
        }
    }
    sort(ans.begin(),ans.end());
    ans.erase(unique(ans.begin(),ans.end()),ans.end());
    for(auto &x:ans) cout<<x<<' ';
    cout<<endl;
}
posted @ 2025-05-31 00:43  usedchang  阅读(30)  评论(0)    收藏  举报