1157 Round 555 DIV3 :F. Maximum Balanced Circle (构造算法 + 尺取法(双指针))

6 篇文章 0 订阅
1 篇文章 0 订阅

题目大意:给出一个序列A,要你从这个序列中选出一些元素构造出一个最长的新序列,这个新序列是一个环,且相邻的两个元素差值不超过1。

解法:首先要知道差值为0也是可以的,但是贪心的从个数最多的元素开始构造是错的,需要枚举来构造答案最大的解。普通枚举区间复杂度达到O(n ^ 2),因此要换一种枚举方式:先将原序列排好序,这样能选的元素一定是在连续的区间,首先固定左端点,我们让右端点遍历序列,如果左端点到右端点之间的元素都可以合法的构造出一个新的序列,那么右端点继续往前移动。怎样是构造不出合法的序列呢?这里要先想到怎么构造,才能枚举。因为最大值和最小值的差值可能会大于等于2,但只要我们用中间的元素把它们包起来,使得任意两个差值大于2的元素不直接接触就没事。要包起来要使得跟他差值为1的元素至少要有两个。

我们移动右端点的时候,右端点的值就是当前枚举到的最大值,如果右端点的值与左端点差值小于等于1,直接移动过去,如果大于1了,我们需要元素值比它小1 的元素的个数大于等于2,因此需要一个统计数值个数的数组,当遍历到元素值Ai时,我们要num[Ai - 1] >= 2,因为前面的元素都遍历过,只要遍历的过程一路都满足这个情况,我们可以一直往下,因为序列是有序的(我们已经排过序),前面Ai - 1的情况我们一定已经遍历并且处理过,因此这样判断总是合法的。

如果不满足num[Ai - 1] >= 2,那么当前右端点的前一个端点到左端点是我们得到的第一个答案,接下来不需要右端点再从头开始,因为中间那一段我们已经枚举过,我们可以移动左端点,直到左端点和右端点之间满足约束(差值小于等于1,或者可以被包住(显然被包住是不可能的,因为这正是我们不合法的原因))。
算下来只有两遍扫描,还有一个优化的做法,预处理出排序后每个数值的第一个位置,当然要先去重了。然后当不合法的时候左端点可以跳着走(要么跳到Ai - 1,要么跳到Ai,或者你可以一段一段跳),就不用一个一个走了。

当枚举的右端点不合法的时候,你就可以更新一下答案了,记录一下最长区间的位置,一遍枚举下来就得到了最长的可以构造的区间。

得到可以构造的区间后,怎样构造呢?可以像包菜那样包起来,把最小的数字放在里面,然后外面套大的,再套更大的。
具体来说:可以想象一下回文串,我们从大到小把每个数值放一个放到容器内,放完一遍后,还剩下一些元素,再从小到大挨个全部放入容器(可以想象一下为什么可以这样,其实就是包起来了,也可以里面大外面小这样包)。

PS(做的时候没想这么多东西,写出来才发现,这题要想的东西还挺多的)

#include<bits/stdc++.h>
using namespace std;
const int maxn = 2e5 + 10;
#define pii pair<int,int>
int n;
int a[maxn];
int vis[maxn];
int pos[maxn];
vector<pii> g;
vector<int> h;
vector<int> t;
int main() {
	scanf("%d",&n);
	for(int i = 1; i <= n; i++) {
		scanf("%d",&a[i]);
	}
	sort(a + 1,a + 1 + n,less<int> ());
	for(int i = 1; i <= n; i++) {
		if(!vis[a[i]]) {
			h.push_back(a[i]);
			pos[a[i]] = i;
		}
		vis[a[i]]++;
	}
	int cur = 0;
	int start = pos[h[cur]];
	int cnt = 0;
	int i;
	pii ans = pii(1,1);
	bool f = false;
	for(i = 1; i <= n; i++) {
		if(a[i] - a[start] >= 2 && vis[a[i] - 1] <= 1) {
			if(i - start > ans.second - ans.first + 1)
				ans = pii(start,i - 1);
				if(pos[a[i] - 1] == 0) start = pos[a[i]];
				else start = pos[a[i] - 1];
				//直接跳:因为不合法情况是因为左端点和右端点差值太大,而右端点不够被包住,那么合法情况
				//就只剩这一种,因此可以直接跳过去

			//start = pos[h[++cur]];	
			//一段一段跳,保守的情况,这个是优化中间相同数字直接跳过
			//i--;
			//注意保守的跳法这里i要减一下,因为下次i又自增了,
			//而我们左端点已经变了,还没考虑过新的左端点是否合法
			//不过胆子大,这些都是可以想到不用搞的,只要上面那两句就行
		} 
	}
	if(i - start > ans.second - ans.first + 1)
		ans = pii(start,i - 1);	
	int s = ans.first,end = ans.second;
	memset(vis,0,sizeof(vis));
	for(int i = end; i >= s; i--) {
		if(!vis[a[i]]) t.push_back(a[i]);
		vis[a[i]]++;
	}
	for(int i = 0; i < t.size(); i++)
		vis[t[i]]--;
	for(int i = s; i <= end; i++) {
		if(vis[a[i]]) t.push_back(a[i]);
		vis[a[i]]--;
	}
	printf("%d\n",t.size());
	for(int i = 0; i < t.size(); i++) {
		printf("%d ",t[i]);
	}
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值