【luogu P8330】众数(根号分治)

众数

题目链接:luogu P8330

题目大意

给你一个序列,你可以选择一个区间加一个数,然后要你使得众数的出现次数最多。
输出这个最多次数和可能作为众数的所有值。

思路

首先转化一下变成你选取一个区间,这个区间的众数和两边拼成的序列的众数的和最大,贡献算两半的。

众数嘛,那跟出现次数有关。
那出现次数的和是 \(n\),那我们考虑能否根号分治。

那么首先大于 \(\sqrt{n}\) 的只有 \(\sqrt{n}\) 个,考虑枚举另一个数(可以直接枚举所有的),然后分别考虑这个数在中间和两边的情况。
然后具体一点就是我们可以维护这个数的一个前缀和以快速求区间个数,然后另一个数跟它的分界线最优必定有一种方案是两笔那都是那个另一个数,所以我们只需要枚举另一个数的逐个位置,一个变量记录最优左端点。
两个情况都是差不多的道理,具体可以看看代码。

那你想想这个处理了哪些情况:大的和大的,大的和小的。
那就差小的和小的。
那考虑小的和小的有什么特征:答案不会超过 \(2\sqrt{n}\)

而且小的我们可以直接暴力枚举两个分界点(同上面的感觉),但是这样就要快速计算众数。
那我们考虑枚举满足是小的右端点,维护一个数组 \(S_i\) 表示 \(i\) 为左端点的众数。(只需要看小的部分的)
然后考虑怎么更新,就枚举它对应的数,然后考虑这个数是否会作为众数给这个左端点贡献。
但是它也可能给前面的也贡献啊,不过发现前提是这个要贡献,亦或者说这个贡献是一段的,然后我们每个位置都往前尝试贡献,不行就停下来即可。
然后因为我们限制只能小的部分,那 \(S_i\) 最大也是 \(\sqrt{n}\) 级别的,那每次修改至少加一,总共就加 \(n\sqrt{n}\) 级别次,所以是没问题的。

然后要稍稍注意的一下就是可能是只有两段(就只有分界点),所以一些地方要记得特判一下之类的。

代码

#include<cstdio>
#include<vector>
#include<algorithm>

using namespace std;

const int N = 2e5 + 100;
const int B = 500;
int T, n, a[N], b[N], m, s[N], S[N], id[N], ans[N];
vector <int> pl[N], bg, sm;

void Clear() {
	for (int i = 1; i <= m; i++) pl[i].clear(), ans[i] = 0;
	for (int i = 1; i <= n; i++) S[i] = 0;
	bg.clear(); sm.clear();
}

//x两边 y中间 
void Run1(int x, int y) {
	int lstv = -1e9, lstp = 0;
	for (int i = 0; i < pl[y].size(); i++) { int x = pl[y][i];
		lstv = max(lstv, s[x - 1] - i);
		lstp = max(lstp, lstv + (i + 1) + (s[n] - s[x]));
	}
	ans[x] = max(ans[x], lstp); 
}

//x中间 y两边 
void Run2(int x, int y) {
	int lstv = 0, lstp = 0;//lstv=0是开头可以是空的 
	for (int i = 0; i < pl[y].size(); i++) { int x = pl[y][i];
		lstp = max(lstp, lstv + s[x - 1] + (int)pl[y].size() - i);
		lstv = max(lstv, i + 1 - s[x]);
	}
	lstp = max(lstp, lstv + s[n]);//结尾也可以是空的 
	ans[y] = max(ans[y], lstp);
}

void work_big() {
	for (int i = 0; i < bg.size(); i++) { int x = bg[i];
		for (int j = 1; j <= n; j++) s[j] = s[j - 1] + (a[j] == x);
		for (int j = 1; j <= m; j++) if (x != j) Run1(x, j), Run2(x, j);
	}
}

void work_small() {
	for (int i = 1; i <= n; i++) if (pl[a[i]].size() <= B) {//右端点
		int val = -1e9;
		for (int j = -1; j < id[i]; j++) { int x = (j == -1) ? 0 : pl[a[i]][j];//左端点 
			val = max(val, j + 1 + S[x + 1] + (int)pl[a[i]].size() - id[i]);
		}
		ans[a[i]] = max(ans[a[i]], val);
		//维护 S
		for (int j = id[i]; j >= 0; j--) {
			int now = pl[a[i]][j];
			while (now && S[now] < id[i] - j + 1)
				S[now--] = id[i] - j + 1;
		} 
	}
	int val = 0;
	for (int i = n; i >= 1; i--) {
		ans[a[i]] = max(ans[a[i]], val + id[i] + 1);
		val = max(val, (int)pl[a[i]].size() - id[i]);
	}
}

void write_ans() {
	int big = 0;
	for (int i = 1; i <= m; i++) if (ans[i] > big) big = ans[i];
	printf("%d\n", big);
	for (int i = 1; i <= m; i++) if (ans[i] == big) printf("%d\n", b[i]);
}

int main() {
//	freopen("mode_ex2.in", "r", stdin);
//	freopen("mode_ex2.out", "w", stdout);
	
	scanf("%d", &T);
	while (T--) {
		scanf("%d", &n); for (int i = 1; i <= n; i++) scanf("%d", &a[i]), b[i] = a[i];
		sort(b + 1, b + n + 1); m = unique(b + 1, b + n + 1) - b - 1;
		for (int i = 1; i <= n; i++) a[i] = lower_bound(b + 1, b + m + 1, a[i]) - b, id[i] = pl[a[i]].size(), pl[a[i]].push_back(i);
		
		for (int i = 1; i <= m; i++) if (pl[i].size() >= B) bg.push_back(i); else sm.push_back(i);
		work_big();
		work_small();
		write_ans();
		
		Clear();
	}
	
	return 0;
} 
posted @ 2022-06-09 01:04  あおいSakura  阅读(42)  评论(0编辑  收藏  举报