CF1446D1, D2 Frequency Problem

CF1446D1, D2 Frequency Problem

题目来源:Codeforces, Codeforces Round #683 (Div. 1, by Meet IT),CF1446D1 Frequency Problem (Easy Version),CF1446D2 Frequency Problem (Hard Version)

题目大意

题目链接

有一个长度为 \(n\) 的序列 \(a_1,a_2,\dots ,a_n\)

我们定义一个子段(连续子序列)是好的,当且仅当其中出现次数最多的元素不唯一。

请求出,最长的、好的子段的长度。

数据范围:\(1\leq n\leq 2\times10^5\)。对于 D1,\(1\leq a_i\leq \min(n,100)\),对于 D2,\(1\leq a_i\leq n\)

本题题解

D1

如果原序列里,出现次数最多的数不唯一,则答案就是 \(n\)。否则,考虑那个唯一的、出现次数最多的元素,记为 \(x\)

发现,最长的、好的子段(也就是答案),有个必要条件是:\(x\) 是其中出现次数最多的元素之一。

证明:考虑一个好的子段 \([l,r]\),如果 \(x\) 不是其中出现次数最多的元素。那么,我们依次考虑 \([l-1,r]\), \([l-2,r]\), ... ..., \([1,r]\), \([1,r+1]\), \([1,r+2]\), ... ..., \([1,n]\)。因为 \(x\) 的数量每次最多增加 \(1\) 个,且它是整个序列里出现次数最多的元素。那么总有某一步后,\(x\) 和序列里其他出现最多的元素一样多。

我们枚举,答案的子段里,和 \(x\) 出现次数一样多的元素是谁,记为 \(y\)。问题转化为,对一组数值 \((x,y)\) 计算答案。

我们可以忽略数列里除 \(x\), \(y\) 外的其他数。问题进一步转化为:求一个最长的子段,满足 \(x\), \(y\) 的出现次数一样多。

把数列里的 \(x\) 看做 \(1\)\(y\) 看做 \(-1\),其他数看做 \(0\)。得到一个序列 \(b\)。那么,我们要求最长的、和为 \(0\) 的子段长度。做前缀和,令 \(\text{sum}_i=\sum_{j=1}^{i}b_j\)。问题相当于找两个位置 \(0\leq l < r\leq n\),满足 \(\text{sum}_r = \text{sum}_l\)。对每个 \(\text{sum}\)\(v\)\([-n,n]\),共 \(2n+1\) 个值)记录它最早的出现位置。然后枚举 \(r\),并更新答案即可。

有读者可能对“忽略数列里除 \(x\), \(y\) 外的其他数”这一做法心存疑虑。因为有可能,我们找到的序列里,存在一个其他数值 \(z\),出现次数多于 \(x,y\)。但是,基于以下两点:

  1. 存在这样 "\(z\)" 的子段,长度一定小于答案。证明:我们按和上面同样的方法,向两边扩展一下,直到 \(x\)\(z\) 出现次数相等。得到的序列仍然满足:存在两个数 \((x,z)\) 出现次数相等,且新的子段长度比原来长。所以只要存在这样的 "\(z\)",我们就扩展一下,最终一定能得到没有 "\(z\)" 的,也就是以 \(x\) 为出现次数最多的数的子段,且长度一定严格大于原来的子段。
  2. 答案的子段,一定会被考虑到。因为只要满足 \(x\), \(y\) 出现次数相等,就会被考虑到。

我们用这种方法,求出的就是答案。

时间复杂度 \(O(n\times\text{maxa})=O(n\times 100)\)。可以通过 D1。

D2

考虑答案序列里,出现次数最多的数,的出现次数。如果出现次数 \(>\sqrt{n}\),则满足条件的数值只有不超过 \(\sqrt{n}\) 个,我们套用 D1 的做法,时间复杂度 \(O(n\sqrt{n})\)

如果出现最多的数,出现次数 \(\leq \sqrt{n}\),我们枚举这个出现次数,记为 \(t\)。然后做 two pointers。

具体来说,枚举区间的右端点 \(r\)。对每个 \(r\),记最小的,使得 \([l,r]\) 内所有数出现次数都 \(\leq t\)\(l\)\(L_r\)。容易发现,对于 \(r=1,2,\dots,n\)\(L_r\) 单调不降。并且,在当前的 \(t\) 下,对一个 \(r\) 来说,如果 \([L_r,r]\) 不是好的子段(出现次数为 \(t\) 的数值,不到 \(2\) 个),那么其他 \([l,r]\) (\(L_r<l\leq r\)) 也不可能是。因为出现次数为 \(t\) 的数值,只会更少。

所以,随着我们从小到大枚举 \(r\),只需要用一个变量来记录当前的 \(L_r\)。同时用数组维护一下,当前每个数的出现次数,以及每个出现次数的出现次数。就能确定,当前的 \([L_r,r]\) 是否是好的区间。具体可以见代码。这个 two pointers 的过程是 \(O(n)\) 的。因为有 \(\sqrt{n}\)\(t\),所以这部分的时间复杂度也是 \(O(n\sqrt{n})\)

总时间复杂度 \(O(n\sqrt{n})\)

总结

本题首先需要一个结论。通过观察、分析题目性质,是可以想到的。

然后需要一个技巧:把一个普通序列,转化为只含 \(0\), \(1\), \(-1\) 的序列。这样就把复杂的、出现次数的关系,转化为了简单的数值关系。这个技巧在一些经典的二分中位数的题里也有用到(把 \(> \text{mid}\) 的视为 \(1\)\(\leq \text{mid}\) 的视为 \(-1\))。

D2,想到根号分治以后,应该就不是太难了。

参考代码

D1:

// problem: CF1446D1
#include <bits/stdc++.h>
using namespace std;

#define pb push_back
#define mk make_pair
#define lob lower_bound
#define upb upper_bound
#define fi first
#define se second
#define SZ(x) ((int)(x).size())

typedef unsigned int uint;
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int, int> pii;

template<typename T> inline void ckmax(T& x, T y) { x = (y > x ? y : x); }
template<typename T> inline void ckmin(T& x, T y) { x = (y < x ? y : x); }

const int MAXN = 2e5;
const int INF = 1e9;
int n, mxa, a[MAXN + 5];
int cnt[MAXN + 5], mp[MAXN * 2 + 5];
int main() {
	cin >> n;
	for (int i = 1; i <= n; ++i) {
		cin >> a[i];
		ckmax(mxa, a[i]);
		cnt[a[i]]++;
	}
	int mx = 0, semx = 0;
	for (int i = 1; i <= mxa; ++i) {
		if (cnt[i] > cnt[mx]) {
			semx = mx;
			mx = i;
		} else if (cnt[i] > cnt[semx]) {
			semx = i;
		}
	}
	// cerr << mx << " " << semx << endl;
	if (!semx) {
		// 整个序列只有一种值
		cout << 0 << endl;
		return 0;
	}
	if (cnt[semx] == cnt[mx]) {
		cout << n << endl;
		return 0;
	}
	int ans = 0;
	for (int v = 1; v <= mxa; ++v) {
		if (v == mx)
			continue;
		if (!cnt[v])
			continue;
		for (int i = 0; i <= n * 2; ++i) {
			mp[i] = INF;
		}
		mp[n] = 0;
		int presum = 0;
		for (int i = 1; i <= n; ++i) {
			int x = 0;
			if (a[i] == mx) x = 1;
			else if (a[i] == v) x = -1;
			presum += x;
			ckmax(ans, i - mp[presum + n]);
			ckmin(mp[presum + n], i);
		}
	}
	cout << ans << endl;
	return 0;
}

D2:

// problem: CF1446D2
#include <bits/stdc++.h>
using namespace std;

#define pb push_back
#define mk make_pair
#define lob lower_bound
#define upb upper_bound
#define fi first
#define se second
#define SZ(x) ((int)(x).size())

typedef unsigned int uint;
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int, int> pii;

template<typename T> inline void ckmax(T& x, T y) { x = (y > x ? y : x); }
template<typename T> inline void ckmin(T& x, T y) { x = (y < x ? y : x); }

const int MAXN = 2e5;
const int INF = 1e9;
const int BOUND = 448;

int n, mxa, a[MAXN + 5];
int cnt[MAXN + 5], mp[MAXN * 2 + 5];

int main() {
	cin >> n;
	for (int i = 1; i <= n; ++i) {
		cin >> a[i];
		ckmax(mxa, a[i]);
		cnt[a[i]]++;
	}
	int mx = 0, semx = 0;
	for (int i = 1; i <= mxa; ++i) {
		if (cnt[i] > cnt[mx]) {
			semx = mx;
			mx = i;
		} else if (cnt[i] > cnt[semx]) {
			semx = i;
		}
	}
	// cerr << mx << " " << semx << endl;
	if (!semx) {
		// 整个序列只有一种值
		cout << 0 << endl;
		return 0;
	}
	if (cnt[semx] == cnt[mx]) {
		cout << n << endl;
		return 0;
	}
	int ans = 0;
	for (int v = 1; v <= mxa; ++v) {
		if (v == mx)
			continue;
		if (cnt[v] <= BOUND)
			continue;
		for (int i = 0; i <= n * 2; ++i) {
			mp[i] = INF;
		}
		mp[n] = 0;
		int presum = 0;
		for (int i = 1; i <= n; ++i) {
			int x = 0;
			if (a[i] == mx) x = 1;
			else if (a[i] == v) x = -1;
			presum += x;
			ckmax(ans, i - mp[presum + n]);
			ckmin(mp[presum + n], i);
		}
	}
	for (int frq = 1; frq <= BOUND; ++frq) {
		for (int i = 1; i <= mxa; ++i) {
			cnt[i] = 0;
		}
		for (int i = 1; i <= n; ++i) {
			mp[i] = 0;
		}
		int l = 1;
		for (int r = 1; r <= n; ++r) {
			mp[cnt[a[r]]]--;
			cnt[a[r]]++;
			mp[cnt[a[r]]]++;
			
			while (cnt[a[r]] > frq) {
				mp[cnt[a[l]]]--;
				cnt[a[l]]--;
				mp[cnt[a[l]]]++;
				++l;
			}
			if (mp[frq] >= 2) {
				ckmax(ans, r - l + 1);
			}
		}
	}
	cout << ans << endl;
	return 0;
}
posted @ 2020-11-19 21:01  duyiblue  阅读(1007)  评论(0编辑  收藏  举报