zoukankan      html  css  js  c++  java
  • 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)

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

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

    数据范围:(1leq nleq 2 imes10^5)。对于 D1,(1leq a_ileq min(n,100)),对于 D2,(1leq a_ileq 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) 的子段长度。做前缀和,令 ( ext{sum}_i=sum_{j=1}^{i}b_j)。问题相当于找两个位置 (0leq l < rleq n),满足 ( ext{sum}_r = ext{sum}_l)。对每个 ( ext{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 imes ext{maxa})=O(n imes 100))。可以通过 D1。

    D2

    考虑答案序列里,出现次数最多的数,的出现次数。如果出现次数 (>sqrt{n}),则满足条件的数值只有不超过 (sqrt{n}) 个,我们套用 D1 的做法,时间复杂度 (O(nsqrt{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<lleq r)) 也不可能是。因为出现次数为 (t) 的数值,只会更少。

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

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

    总结

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

    然后需要一个技巧:把一个普通序列,转化为只含 (0), (1), (-1) 的序列。这样就把复杂的、出现次数的关系,转化为了简单的数值关系。这个技巧在一些经典的二分中位数的题里也有用到(把 (> ext{mid}) 的视为 (1)(leq ext{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;
    }
    
  • 相关阅读:
    面向对象的继承关系体现在数据结构上时,如何表示
    codeforces 584C Marina and Vasya
    codeforces 602A Two Bases
    LA 4329 PingPong
    codeforces 584B Kolya and Tanya
    codeforces 584A Olesya and Rodion
    codeforces 583B Robot's Task
    codeforces 583A Asphalting Roads
    codeforces 581C Developing Skills
    codeforces 581A Vasya the Hipster
  • 原文地址:https://www.cnblogs.com/dysyn1314/p/14007922.html
Copyright © 2011-2022 走看看