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