[ZJOI2022]众数

众数

题解

F u t a R i m e W o a w a S e t e \rm F\color{red}{utaRimeWoawaSete} FutaRimeWoawaSete说了说这道题,就去做了一下。说不定是今年ZJOI唯一一道签到题。

由于它要输出可能的最大值并将所有可能的最大值都输出,那么我们显然都要尽可能求出某个数可能的最多出现次数。
由于我们的 a i a_i ai并不全相等,所以我们最后的可能成为众数的值一定是一个原先出现过的数,而且这个值必然是将原来一个区间 ( l , r ) (l,r) (l,r)中的数 x x x变到区间外的数 y y y而得到的。
如果只是某一个数变成的话,我们不如不变那一个数,将其它数变到它身上,这样反而能够得到更大的答案。

如果数 x x x出现的位置时 { p 0 = 0 , p 1 , p 2 , . . . , p k − 1 , p k = n + 1 } \{p_0=0,p_1,p_2,...,p_{k-1},p_k=n+1\} {p0=0,p1,p2,...,pk1,pk=n+1},记区间 ( l , r ) (l,r) (l,r)中出现次数最多的数出现了 c ( l , r ) c(l,r) c(l,r)次,那么 x x x最后的答案便是 max ⁡ 0 ⩽ i ⩽ j ⩽ k ( k − j + i + c ( p i , p j ) ) \max_{0\leqslant i\leqslant j\leqslant k}(k-j+i+c(p_i,p_j)) max0ijk(kj+i+c(pi,pj))
一种可行的方法是枚举出现在 ( p i , p j ) (p_i,p_j) (pi,pj)中出现次数最多的数为 x x x
记前缀 i i i中出现了 p r e i pre_i prei x x x,显然,我们可以改写一下上面的式子,
a n s x = k + max ⁡ 0 ⩽ i ⩽ j ⩽ k ( ( p r e p i + i ) − ( p r e p j + j ) ) ans_x=k+\max_{0\leqslant i\leqslant j\leqslant k} \left((pre_{p_i}+i)-(pre_{p_j}+j)\right) ansx=k+0ijkmax((prepi+i)(prepj+j))这样不就只需要记录下一个前缀最小的 p r e p i + i pre_{p_i}+i prepi+i,就行了吗?
于是我们就得到了一个 O ( n 2 ) O\left(n^2\right) O(n2)做法了吗,但显然是过不了 n ⩽ 2 × 1 0 5 n\leqslant 2\times 10^5 n2×105的数据的。

没事,我们继续想想上面方法的瓶颈主要是在什么地方。
显然这个方法我们对于每个颜色作为中间的数都要将整个序列扫一遍,而许多出现次数很少的颜色其实是完全没必要扫一遍的。
不如考虑根号分治,我们对出现次数很少的颜色想另外的做法。
由于这些颜色出现不超过 B B B次,不妨就枚举我们区间内出现最多的颜色出现 i ∈ [ 1 , B ] i\in[1,B] i[1,B]次时的答案。
显然,对于每个左端点,当右端点为 r r r时,最右边的左端点 l l l使得 ( l , r ) (l,r) (l,r)中有颜色出现 i i i次的这个 l l l显然是关于 r r r单增的。
这样我们就可以只需要知道在 [ 1 , l ] ∪ [ r , n ] [1,l]\cup[r,n] [1,l][r,n]中出现次数最多的颜色的出现次数就行。
显然,对于任意的一种颜色,我们让 a r a_r ar刚好为这种颜色显然是最优的,那么我们需要知道的其实是 [ 1 , l ] ∩ [ r , n ] [1,l]\cap[r,n] [1,l][r,n]中出现了多少个颜色 a r a_r ar即可。
这个由于我们的 r r r是不断右移的,所以我们也可以对颜色 a r a_r ar设计一个指针,可以用前向星维护,让他跑到在 l l l右边的最后一个 a r a_r ar处,这样就可以很简单的统计这东西了。
我们也就用 O ( n B ) O\left(nB\right) O(nB)的时间完成了出现次数少于 B B B的颜色的答案统计。

总时间复杂度 O ( n 2 B + n B ⩾ n n ) O\left(\frac{n^2}{B}+nB\geqslant n\sqrt{n}\right) O(Bn2+nBnn ),让 B B B n \sqrt{n} n 时最优。

源码

#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
typedef pair<int,int> pii;
#define MAXN 200005
#define MAXM (1<<18)+5
#define pb push_back
#define mkpr make_pair
#define fir first
#define sec second
const int INF=0x3f3f3f3f;
const int mo=1e9+7;
const int iv2=5e8+4;
const int lim=1000000;
const int n1=300;
template<typename _T>
void read(_T &x){
	_T f=1;x=0;char s=getchar();
	while(s<'0'||s>'9'){if(s=='-')f=-1;s=getchar();}
	while('0'<=s&&s<='9'){x=(x<<3)+(x<<1)+(s^48);s=getchar();}
	x*=f;
}
template<typename _T>
_T Fabs(_T x){return x<0?-x:x;}
int add(int x,int y,int p){return x+y<p?x+y:x+y-p;}
void Add(int &x,int y,int p){x=add(x,y,p);}
int qkpow(int a,int s,int p){int t=1;while(s){if(s&1)t=1ll*a*t%p;a=1ll*a*a%p;s>>=1;}return t;}
int T,n,a[MAXN],b[MAXN],cnt[MAXN],maxx[MAXN];
int pre[MAXN],nxt[MAXN],head[MAXN],dp[MAXN];
int tot,buc[MAXN],pos[MAXN],id[MAXN],ans;
int main(){
	read(T);
	while(T--){
		read(n);for(int i=1;i<=n;i++)read(a[i]),b[++tot]=a[i];
		sort(b+1,b+tot+1);tot=unique(b+1,b+tot+1)-b-1;
		for(int i=1;i<=n;i++)a[i]=lower_bound(b+1,b+tot+1,a[i])-b;
		for(int i=1;i<=n;i++)cnt[a[i]]++,id[i]=cnt[a[i]];
		for(int i=1;i<=tot;i++)maxx[i]=cnt[i];
		for(int i=n;i>0;i--)nxt[i]=head[a[i]],pre[head[a[i]]]=i,head[a[i]]=i;
		for(int i=1;i<=tot;i++)if(cnt[i]>n1)
			for(int j=1,num=0;j<=n;j++){
				const int x=a[j];
				if(x==i)num++;
				else maxx[x]=max(dp[pre[j]]+num+cnt[x]-id[j]+1,maxx[x]),
					dp[j]=max(dp[pre[j]],id[j]-num),
					maxx[x]=max(maxx[x],dp[j]+cnt[i]);
			}
		for(int i=min(n1,n);i>0;i--){
			int num=0,l=1;
			for(int j=1;j<=tot;j++)buc[j]=0,pos[j]=head[j];
			for(int j=1;j<=n;j++){
				const int x=a[j];buc[x]++;if(buc[x]==i)num++;
				while(l<=j&&num>(buc[a[l]]==i)){
					if(buc[a[l]]==i)num--;
					buc[a[l]]--;l++;
				}
				while(nxt[pos[x]]&&nxt[pos[x]]<l)pos[x]=nxt[pos[x]];
				int tmp=cnt[x]-id[j]+1+id[pos[x]]-(pos[x]>=l);
				if(num)maxx[x]=max(maxx[x],tmp+i);
			}
			for(int j=1;j<=tot;j++){
				while(nxt[pos[j]]&&nxt[pos[j]]<l)pos[j]=nxt[pos[j]];
				if(num)maxx[j]=max(maxx[j],id[pos[j]]+i-(pos[j]>=l));
			}
		}
		int mx=0;
		for(int i=1;i<=tot;i++)mx=max(mx,maxx[i]);
		printf("%d\n",mx);
		for(int i=1;i<=tot;i++)if(maxx[i]==mx)printf("%d\n",b[i]);
		for(int i=1;i<=tot;i++)head[i]=cnt[i]=0;tot=0;
		for(int i=1;i<=n;i++)pre[i]=nxt[i]=id[i]=0;
	}
	return 0;
}

谢谢!!!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值