对于一道贪心题,关键是要证明,如果数据范围给好一点,估计很容易想到贪心
因为数据有点大,直接处理可能会TLE(虽然有人直接处理也过了,但秉着探索科学的心,我们来想想正解)
实际上我已经想过两两合并,但忽略了最长子段和跨越的情况
对于序列贪心问题,一定要想到合并两个小序列
这样才能线性递推
数组操作
基础题:
最大子段和
考虑直接扫描的过程遇到小于0就重新赋值为0,
则我们可以更新一个数组中间段的最大值(最大子段和)
当两个数组合并能产生最大值的时候,既满足最大前缀和和最大后缀和的值最大时可以更新最大值
然后我们考虑合并这两个数组,
1.最大前缀和
最大前缀和可以是后一个数组的最大前缀和(从第二个数组内不能从前往后选出和比最大前缀和的和),也可以是前一个数组的后缀和(从第一个数组从后往前不能选出和比最大后缀和还大的值)
2.最大后缀和
同理,分两种情况讨论
3.最大子段和
前一个数组最大子段和和后一个数组最大子段和,以及最大后缀和和最大前缀和的和最大值
- 都不难理解,用反证法可以证明
- 以3为例,首先在两个数组中,最大子串和肯定是两个数组的最大子段和
- 考虑在两段合并之后的:
- 若第一个数组不取最大后缀和,记取得的和为s1,最大后缀和为suf1,则s1<suf1,
- 最大前缀和同理,s2<suf2
- 则s1+s2<suf1+suf2
- 矛盾,证毕
sums=sum[quq[1]][1],pre[0]=sum[quq[1]][0],pre[1]=sum[quq[1]][2],ans=max(sum[quq[1]][1],max(pre[1],pre[0]));
sumss=sum[quq[1]][3];
for(int i=2;i<=m;i++){
int a=quq[i];
ans=max(max(ans,sum[a][1]),max(pre[1],pre[0]));
sums=max(max(pre[1]+sum[a][0],sum[a][1]),sums);
ans=max(ans,sums);
pre[0]=max(pre[0],sumss+sum[a][0]);
pre[1]=max(sum[a][2],pre[1]+sum[a][3]);
ans=max(ans,max(pre[0],pre[1]));
sumss+=sum[a][3];
}
所以我们只需要维护最大前缀和,最大后缀和,最大子段和
两个数组,因为直接扫描的时候可以直接更新一个数组的最大前缀和,最大后缀和,最大子段和,数组(方便更新)
所以合并的时候也要维护这3个值
AC代码:
#include<bits/stdc++.h>
using namespace std;
const int N=51,L=5010,M=250010;
int n,m;int quq[M];int sum[N][4];int in[N][L];
int sums,pre[2],sumss;
int main(){
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++){//
int l;
scanf("%d",&l);
int s=0;
for(int j=1;j<=l;j++)
{
scanf("%d",&in[i][j]); sum[i][3]+=in[i][j];
}
for(int j=1;j<=l;j++)
{ s+=in[i][j];
sum[i][0]=max(sum[i][0],s);
}
s=0;
for(int j=l;j>=1;j--)
{
s+=in[i][j];
sum[i][2]=max(sum[i][2],s);
}
s=0;
for(int j=1;j<=l;j++)
{
s+=in[i][j];
if(s<0) {s=0;continue;};
sum[i][1]=max(sum[i][1],s);
}
}
for(int i=1;i<=m;i++){scanf("%d",&quq[i]);}
int ans;
sums=sum[quq[1]][1],pre[0]=sum[quq[1]][0],pre[1]=sum[quq[1]][2],ans=max(sum[quq[1]][1],max(pre[1],pre[0]));
sumss=sum[quq[1]][3];
for(int i=2;i<=m;i++){
int a=quq[i];
ans=max(max(ans,sum[a][1]),max(pre[1],pre[0]));
sums=max(max(pre[1]+sum[a][0],sum[a][1]),sums);
ans=max(ans,sums);
pre[0]=max(pre[0],sumss+sum[a][0]);
pre[1]=max(sum[a][2],pre[1]+sum[a][3]);
ans=max(ans,max(pre[0],pre[1]));
sumss+=sum[a][3];
}
printf("%d",ans);
}
另一份AC代码(没看题解写的,调了很久)
这说明了最大前缀和,最大字段和的求法是不一样的,不可以一起求
因为最大子段和可以从任意位置开始,可以贪心策略,而最大前缀和不可以从任意位置开始,所以小于0时不能重新赋值为0
#include<bits/stdc++.h>
using namespace std;
const int N=51,L=5010,M=250010;
int n,m;int quq[M];int sum[N][4];int in[N][L];
int sums,pre[2],sumss;
int main(){
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++){
int l;
scanf("%d",&l);
int s=0;
for(int j=1;j<=l;j++)
{
scanf("%d",&in[i][j]);
sum[i][3]+=in[i][j];
s+=in[i][j];
sum[i][1]=max(sum[i][1],s);
if(s<0) {
s=0;
}
if(j==l){
sum[i][2]=max(sum[i][2],s);
}
}
s=0;//分开求
for(int j=1;j<=l;j++){
s+=in[i][j];
sum[i][0]=max(sum[i][0],s);
}
}
for(int i=1;i<=m;i++){
scanf("%d",&quq[i]);
}
int ans;
sums=sum[quq[1]][1],pre[0]=sum[quq[1]][0],pre[1]=sum[quq[1]][2],ans=max(sum[quq[1]][1],max(pre[1],pre[0]));
sumss=sum[quq[1]][3];
for(int i=2;i<=m;i++){
int a=quq[i];
ans=max(max(ans,sum[a][1]),max(pre[1],pre[0]));
sums=max(max(pre[1]+sum[a][0],sum[a][1]),sums);
ans=max(ans,sums);
pre[0]=max(pre[0],sumss+sum[a][0]);
pre[1]=max(sum[a][2],pre[1]+sum[a][3]);
ans=max(ans,max(pre[0],pre[1]));
sumss+=sum[a][3];
}
printf("%d",ans);
}