P9460 众数 I
思路
这道题可以改序列的 \(k\) 个数,问有多少个数字可以变成众数。
那么我们可以检查每个数字,来判断这个数字是否可以在序列被更改后作为众数。
从最优的思路出发,我们每次肯定是把数量最多的一个数字变成想要的数字,这样才能保证进行的操作最少,但是需要考虑的是原序列出现最多次数的数字可能不止一个,比如序列:\(1,1,1,2,2,2\),出现了三次的数字有两个,所以判断条件也绝对不是 \(num[i]+k>=maxn-k\)。
而还需要注意的是,序列里不仅仅有出现次数最多的数字,还有次多的数字,所以想要 \(O(1)\),判断每个数字是否可行是几乎不可能的,我们只能从模拟的思路出发,每次将当前数字的数量加上目前最多数的个数,然后将目前最多数 \(-1\)(代表每个最多数都拿一个给i),再加上目前最大数原本的个数,直到操作次数被用完为止。
赛时代码(有点乱qwq)
#include<bits/stdc++.h>
using namespace std;
int n,k,a[1000005],num[1000005],nnum[1000005],maxn,ans,nn;
int main()
{
cin>>n>>k;
if(k>=n){cout<<"pigstd";return 0;}//小特判,不过应该不用的。
for(int i=1;i<=n;i++)
{
cin>>a[i];
num[a[i]]++;//第一次桶排,统计出每个数字出现的次数
}
for(int i=1;i<=n;i++)
{
if(maxn<num[i]) maxn=num[i],nn=1;else if(maxn==num[i]) nn++;//统计最多次数和最多的有多少个数字
nnum[num[i]]++;//统计每个出现次数的出现次数(桶排的桶排)
}
if(k<=nn)//分了个类,如果满足条件,则操作数用完最大次数要么不变,要么就是原来的减一,这样的话就和原来次多的数无关了
{
if(k>=maxn-k/nn){cout<<"pigstd";return 0;}//判断原来有0个的数字可不可以变成众数,如果可以就代表所有数都可以变成众数。
for(int i=1;i<=n;i++)
{
if(num[i]+k>=maxn-k/nn) ans++;//判断这个数可不可以成为众数。
}
cout<<ans;return 0;
}
int tk=k,tmax=maxn,u,tn;//tk:临时k,tmax:临时最大值,u:当前数的数量,tn:当前最多数的个数
for(int i=1;i<=n+1;i++)
{
tk=k,tmax=maxn,u=num[i],tn=nn;//更新最新的消息
if(u>=tmax){ans++;continue;}//如果当前数量已经超过了最多数量的话,这个数是可以变成众数的。
while(tk)
{
if(u>=tmax){ans++;if(num[i]==0){cout<<"pigstd";return 0;}break;}//判断同上,特判了0(好像没用,比赛时考虑不了那么多,随手就写上去了qwq
if(tk<tn){//如果没办法把当前所有的最多数变成i。
u+=tk;
if(u>=tmax){ans++;if(num[i]==0){cout<<"pigstd";return 0;}break;}//立马判断一次,因为此时所有的操作数都用完了。
else break;
}
u+=tn,tk-=tn,tmax--,tn+=nnum[tmax];//相当于每个当前最多数拿一个变成i后的各项数值,自己推一下就知道了。
if(!tk)
{
if(u>=tmax){ans++;if(num[i]==0){cout<<"pigstd";return 0;}}
break;
}//如果操作数是0了,就再判一次,因为下次没办法判了。
}
}
if(ans==n+1){cout<<"pigstd";return 0;}//再特判,如果n+1(因为题中给了最大就是n)可以成为众数,那么所有数都可以
cout<<ans;
return 0;
}