题解 P8330 [ZJOI2022] 众数

众所周知,ZJOI 的签到题一般在 D1T2。

没去 ZJOI,在家口胡的。想了一会似乎就会根号做法了。真难受没得去省选,本来可能会是考场上做出的第一个黑吧。


原题是选择一段区间 \([l,r]\),使得 \([l,r]\) 内的众数次数与除去此段区间外的众数次数的和最大,此时众数答案是 \([l,r]\) 外的任何一个众数。

根号分治。设 \(B=\sqrt n\)。考虑颜色出现次数不超过 \(B\) 的是 A 类,否则是 B 类。我们枚举一下 \([l,r]\) 外的众数是 \(x\)

  1. 内部的众数是 A 类。此时内部的众数次数必然不超过 \(B\)

枚举内部众数出现次数 \(k\),此时只要让 \([l,r]\) 内答案是 \(k\)\(x\) 的出现次数最小就行了。对于每个 \(k\),预处理好位置 \(i\) 的颜色的之前第 \(k\) 个出现位置 \(pre_{i}\)。考虑所有相邻 \(x\) 的位置之间的间隔,找到这里 \(pre\) 的最大值就行了。然后用双指针找一下当前 \(pre\) 的最大值位于哪个间隔,就可以计算。

  1. 内部的众数是 B 类。此时内部的众数种类不超过 \(B\)

枚举当前内部众数是 \(y\)。假设每个 \(x\)\(+1\),每个 \(y\)\(-1\),目标是找到 \(l,r\) 使得 \([1,l]+[r,n]\) 最大。所以 \(l,r\) 必然落在 \(+1\) 上,前缀和一下可以用 \(x\) 的次数次检查完成。

总复杂度 \(O(n\sqrt n)\)

#include<bits/stdc++.h>
using namespace std;
int n,m;
const int N=2e5+5,B=447;
struct point
{
	int x,id;
}b[N];
int a[N],ans[N],c[N],col[N],pre[N],d[N];
int A[N],C[N],cnta,cntc,sum[N];
vector<int>g[N];
bool cmp(point x,point y)
{
	return x.x<y.x;
}
inline int read()
{
	char C=getchar();
	int F=1,ANS=0;
	while (C<'0'||C>'9')
	{
		if (C=='-') F=-1;
		C=getchar();
	}
	while (C>='0'&&C<='9')
	{
		ANS=ANS*10+C-'0';
		C=getchar();
	}
	return F*ANS;
}
void work()
{
	n=read();
	for (int i=1;i<=n;i++) b[i].id=i,b[i].x=read();
	sort(b+1,b+n+1,cmp);
	m=0;
	int gp=0; 
	for (int i=1;i<=n;i++)
	{
		if (i==1||b[i].x!=b[i-1].x) c[++m]=b[i].x;
		a[b[i].id]=m;
	}
	for (int i=1;i<=m;i++) ans[i]=0,col[i]=0;
	for (int i=1;i<=m;i++) g[i].clear();
	for (int i=1;i<=n;i++) col[a[i]]++,g[a[i]].push_back(i),d[i]=col[a[i]];
	cnta=0,cntc=0;
	int b=0;
	for (int i=1;i<=m;i++)
	{
		gp=max(gp,col[i]);
		if (col[i]>B) A[++cnta]=i;
		else C[++cntc]=i,b=max(b,col[i]);
	}
	for (int j=1;j<=cnta;j++)
	{
		int v=A[j];
		for (int i=1;i<=n;i++) sum[i]=0;
		for (int i:g[v]) sum[i]=-1;
		for (int i=1;i<=n;i++) sum[i]+=sum[i-1];
		for (int u=1;u<=m;u++)
		{
			int res=0,tot=0;
			for (int i:g[u])
			{
				ans[u]=max(ans[u],col[v]+col[u]-res+sum[n]-sum[i-1]+tot);
				res++;
				tot=max(tot,res+sum[i]);
			}
			ans[u]=max(ans[u],tot+col[v]);
		}
	}
	int res=0;
	for (int i=1;i<=m;i++) res=max(res,ans[i]);
	for (int k=1;k<=b;k++)
	{
		if (k+gp<res) continue;
		pre[0]=-1;
		for (int i=1;i<=n;i++)
		{
			if (d[i]-k<=-1) pre[i]=-1;
			else pre[i]=g[a[i]][d[i]-k];
		}
		for (int i=2;i<=n;i++) pre[i]=max(pre[i-1],pre[i]);
		for (int u=1;u<=m;u++)
		{
			int res=0,tot=0;
			for (int i:g[u])
			{
				int p=pre[i-1];
				if (p==-1)
				{
					res++;
					continue;
				}
				while (tot<col[u]&&p>g[u][tot]) tot++;
				ans[u]=max(ans[u],k+col[u]-(res-tot));
				res++;
			}
			if (pre[n]!=-1) 
			{
				int p=pre[n];
				while (tot<col[u]&&p>g[u][tot]) tot++;
				ans[u]=max(ans[u],k+col[u]-(res-tot));
			}
		}
	}
	for (int i=1;i<=m;i++) res=max(res,ans[i]);
	printf("%d\n",res);
	for (int i=1;i<=m;i++)
	{
		if (ans[i]==res) printf("%d\n",c[i]);
	}
}
int main()
{
	int T=read();
	while (T--) work();
	return 0;
}
posted @ 2022-06-30 19:55  Little09  阅读(53)  评论(0编辑  收藏  举报