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;
}


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM