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;
    }
    
  • 相关阅读:
    mybatis-redis项目分析
    mybatis-redis项目分析
    redis(一)简介
    redis(一)简介
    redis(二)redis+TCMALLOC高性能的缓存服务器的安装配置
    redis(二)redis+TCMALLOC高性能的缓存服务器的安装配置
    redis(三)redis+Keepalived主从热备秒级切换
    redis(三)redis+Keepalived主从热备秒级切换
    redis(五)redis与Mybatis的无缝整合让MyBatis透明的管理缓存二
    jQuery示例
  • 原文地址:https://www.cnblogs.com/dysyn1314/p/14007922.html
Copyright © 2011-2022 走看看