[ABC162F] Select Half 题解

博客里阅读

题目大意:给定一个长度为\(n\)的数列,让你从中取出\(\lfloor\frac{n}{2}\rfloor\)个互不相邻的数,输出这\(\lfloor\frac{n}{2}\rfloor\)个数的和的最大值

一开始以为就是分类讨论,但最后一个样例怎么都过不去,比赛结束后5min才想到了一个和官方题解的DP不一样的神奇解法...错失涨分的良机

首先考虑\(n\)为偶数的情况,手动模拟一下可以发现只有一种情况,取前面一段为奇数位的数和后面一段为偶数位的数

如(1表示选,0表示不选,接下来讨论的亦如此)

1 0 1 0 0 1 0 1 0 1
0 1 0 1 0 1 0 1

然后就是\(n\)为奇数的情况,这里有一个神奇的性质,我们先来写出几种情况

1 0 1 0 0 0 1
1 0 1 0 1 0 0 1 0 1 0
1 0 1 0 0 1 0 1 0 0 1 0 1

情况多而杂,不好分类讨论,于是我们考虑有没有一种通式能够概括所有的情况,先考虑把奇数位的数全都取上,总共是\(\lfloor\frac{n}{2}\rfloor+1\)个数,只需要减少一个数就可以满足题目的要求

然后试着把全取奇数位的数与我在上面列出来的情况对比一下,注意看加粗的部分

1 0 1 0 1 0 1
1 0 1 0 0 0 1

1 0 1 0 1 0 1 0 1 0 1
1 0 1 0 1 0 0 1 0 1 0

1 0 1 0 1 0 1 0 1 0 1 0 1
1 0 1 0 0 1 0 1 0 0 1 0 1

是不是已经发现了,所有的情况都可以视为将全取奇数位的数进行一个两端为1的区间的01翻转(注意所选的区间不能为空),我们要确定这样的一个区间,使得所取的和最大,这个可以用我们熟悉的最大子段和,时间复杂度\(O(n)\),如果还感觉没理解的话可以再手玩一下

代码很简短,感觉没理解的相信你看一下代码就懂了

#include<bits/stdc++.h>
#define int long long //记得longlong!!
using namespace std;
int n,a[200003],fsum[200003],bsum[200003],ans=-2e18,sum,su; //fsum[i]代表的是与i奇偶性相同的数的前缀和,bsum[i]代表的是与i奇偶性相同的数的后缀和
signed main()
{
	scanf("%lld",&n);
	for(int i=1; i<=n; i++)
		scanf("%lld",&a[i]);
	fsum[1]=a[1],fsum[2]=a[2],bsum[n]=a[n],bsum[n-1]=a[n-1];
	for(int i=3; i<=n; i++)
		fsum[i]=fsum[i-2]+a[i];
	for(int i=n-2; i>=1; i--)
		bsum[i]=bsum[i+2]+a[i];
	sum=fsum[n];
	if(n%2==0)
	{
		ans=max(fsum[n-1],fsum[n]);
		for(int i=1; i<=n; i+=2)
			ans=max(ans,fsum[i]+bsum[i+3]);
		cout<<ans;
		return 0;
	}
	for(int i=1; i<=n; i++)
		if(i%2==0)
			su+=a[i];
		else
			su=max(su-a[i],-a[i]),ans=max(ans,sum+su); //最大子段和
	cout<<ans;
	return 0;
}
posted @ 2020-04-13 12:12  dz_ice  阅读(234)  评论(0编辑  收藏  举报