题解 [CF1446D2] Frequency Problem (Hard Version)

传送门

又是大神仙题

要求的是最大的满足区间众数不止一个的区间长度
并不知道如何用数据结构维护

先证一个结论:整个区间的众数一定是至少一个最后要求的最长区间中的众数
证明考虑反证,不断扩展区间直到整个区间的众数是这个区间中的众数一定更优
然后看有了这个结论怎么写
首先eary version的值域很小,可以枚举值域
那就可以枚举与这个众数出现次数相同的另一个数是什么
令众数为 \(d\),另一个数为 \(k\)
即为要求 \(d, k\) 出现次数相同的最长区间长度,并用其更新答案
注意这里并不要求 \(d, k\) 是区间中的众数,因为不是的话一定会枚举到另一个更优的 \(k\)(比如这个区间中的众数)来更新答案
具体地,若 \(d, k\) 不是区间中的众数,看起来答案好像算多了
但令区间众数为 \(s\),我们发现可以扩展区间使得 \(d\) 的出现次数与 \(s\) 相等(当然在这个过程中也可能出现了其它出现次数更多的数)
此时 \(k\) 已经不是众数,但仍可贡献区间长度
于是我们得到了一个严格大于刚才那个好像算大了的答案的新答案
于是我们不会将答案算大,可以这样统计
实现的话求两个数出现次数相同的最长区间可以令一个数为1,一个为-1,其它数为0
于是就是求和为0的最长区间
于是就是求两个前缀和相等的点的最长距离,记录一下每种前缀和第一次出现的位置即可
这部分复杂度是 \(O(nV)\) 的,\(V\) 为值域

然后再看若值域较大怎么办
还有一种不太好想到的做法是枚举众数在答案区间出现的次数
于是可以双指针尽量扩大区间长度
开桶统计一下每个数在这个区间内出现的次数,并维护出现次数的最大值
因为每次变化都是+1 -1,所以最大值可以 \(O(1)\) 维护
每次check一下区间内最大出现次数是否 \(\geqslant\) 枚举的众数出现次数即可
至于为什么能大于和上面一样
复杂度 \(O(n*\text{众数出现次数})\)

然后优化,可以对每个数的出现次数根号分治
\(> \sqrt n\) 用第一种方法
然后所有剩下的 \(\leqslant \sqrt n\) 的枚举根号个可能长度用第二种方法处理掉
整体复杂度 \(O(n\sqrt n)\)

Code:
#include <bits/stdc++.h>
using namespace std;
#define INF 0x3f3f3f3f
#define N 200010
#define ll long long
//#define int long long

char buf[1<<21], *p1=buf, *p2=buf;
#define getchar() (p1==p2&&(p2=(p1=buf)+fread(buf, 1, 1<<21, stdin)), p1==p2?EOF:*p1++)
inline int read() {
	int ans=0, f=1; char c=getchar();
	while (!isdigit(c)) {if (c=='-') f=-f; c=getchar();}
	while (isdigit(c)) {ans=(ans<<3)+(ans<<1)+(c^48); c=getchar();}
	return ans*f;
}

int n;
int a[N], cnt[N], d, dcnt, ans, len;

namespace task1{
	int sum[N], pos[N<<1];
	void solve(int k) {
		// cout<<"k: "<<k<<endl;
		memset(sum, 0, sizeof(sum));
		memset(pos, -1, sizeof(pos));
		pos[n]=0;
		for (int i=1; i<=n; ++i) {
			if (a[i]==d) sum[i]=sum[i-1]+1;
			else if (a[i]==k) sum[i]=sum[i-1]-1;
			else sum[i]=sum[i-1];
			if (pos[sum[i]+n]!=-1) ans=max(ans, i-pos[sum[i]+n]);
			else pos[sum[i]+n]=i;
		}
		// cout<<"sum: "; for (int i=1; i<=n; ++i) cout<<sum[i]<<' '; cout<<endl;
		// cout<<"pos: "; for (int i=0; i<=n; ++i) cout<<pos[i]<<' '; cout<<endl;
	}
}

namespace task2{
	int cnt[N], buc[N];
	void solve(int k) {
		memset(cnt, 0, sizeof(cnt));
		memset(buc, 0, sizeof(buc));
		buc[0]=n;
		int pos1=1, pos2=0, mcnt=0, tcnt=0;
		while (tcnt<k) {
			if (a[++pos2]==d) ++tcnt;
			else {
				--buc[cnt[a[pos2]]];
				++buc[++cnt[a[pos2]]];
				mcnt=max(mcnt, cnt[a[pos2]]);
			}
		}
		while (pos2<n && a[pos2+1]!=d) {
			++pos2;
			--buc[cnt[a[pos2]]];
			++buc[++cnt[a[pos2]]];
			mcnt=max(mcnt, cnt[a[pos2]]);
		}
		if (mcnt>=k) ans=max(ans, pos2-pos1+1);
		// cout<<"pos: "<<pos1<<' '<<pos2<<endl;
		while (pos2<n) {
			++pos2;
			while (pos2<n && a[pos2+1]!=d) {
				++pos2;
				--buc[cnt[a[pos2]]];
				++buc[++cnt[a[pos2]]];
				mcnt=max(mcnt, cnt[a[pos2]]);
			}
			while (a[pos1]!=d) {
				--buc[cnt[a[pos1]]];
				++buc[--cnt[a[pos1]]];
				if (!buc[mcnt]) --mcnt;
				++pos1;
			}
			++pos1;
			if (mcnt>=k) ans=max(ans, pos2-pos1+1);
		}
	}
}

signed main()
{
	n=read(); len=sqrt(n);
	for (int i=1; i<=n; ++i) ++cnt[a[i]=read()];
	for (int i=1; i<=n; ++i) if (cnt[i]>dcnt) dcnt=cnt[i], d=i;
	for (int i=1; i<=n; ++i) if (cnt[i]&&i!=d&&cnt[i]>len) task1::solve(i);
	for (int i=1; i<=min(len, dcnt); ++i) task2::solve(i); //, cout<<"after"<<i<<"ans="<<ans<<endl;
	printf("%d\n", ans);

	return 0;
}
posted @ 2021-11-09 08:28  Administrator-09  阅读(0)  评论(0编辑  收藏  举报