题意简述
给定 (n(n<=50)) 个长度不超过 (l(l<=5000)) 的序列,以及一个长度为 (m(m<=250000)) 的索引序列,每个索引表示某一序列的编号,然后按照这 (m) 个索引的顺序,将小序列组成一个大序列,求这个大序列的最大子段和。
算法概述
首先看到数据范围,就知道暴力求大序列肯定不行。
考虑大序列的最大子段和的组成,无非以下两种情况:
- 某个小序列的最大子段和;
- 从某个小序列开始,到某个小序列结束。
对于第一种情况,我们只需对每个小序列都求一遍最大子段和,然后取 (m) 个小序列中的最大值即可。
主要看第二种情况,我们可以设 (f[i]) 表示以第 (i(1<=i<=m)) 个小序列为结尾的最大子段和。
考虑枚举 (j),表示以第 (j) 个小序列为开头,则整段最大子段和即为第 (j) 个小序列的最大后缀和,加上第 (j+1) 到第 (i-1) 个序列的和,再加上第 (i) 个序列的最大前缀和。于是我们就得到了状态转移方程:
(f[i]=max(r[j]+sum[i-1]-sum[j]+l[i]))
其中 (sum[i]) 表示从第 (1) 个小序列首位开始到第 (i) 个小序列的末位为止的所有数的总和,(l[i]) 表示第 (i) 个小序列的最大前缀和(至少包含 (1) 个数),(r[i]) 表示第 (i) 个小序列的最大后缀和(至少包含 (1) 个数),由于题目要求最大子段和至少要选择一个数,所以最大前缀和与最大后缀和均不能为空。
将状态转移方程整理得:
(f[i]=sum[i-1]+l[i]+max(r[j]-sum[j]))
我们发现,当 (i) 固定的时候,我们 (sum[i-1]+l[i]) 也就固定了,我们只需得到 (r[j]-sum[j]) 的最大值即可,而 (j) 的范围是 (1<=j<=i-1),可以发现当 (i) 增大时,(j) 的范围只有右边界会发生改变,如果我们每次计算 (f[i]) 的时候都将 (j) 全部枚举一遍的话,会产生很多冗余计算,浪费时间。故我们没有必要循环枚举 (j),只需用一个变量 (maxv) 记录从1到当前为止的 (r-sum) 的最大值,在循环 (i) 的时候,先计算 (f[i]) ,再将当前的 (r[i]-sum[i]) 与 (maxv) 比较,取较大值更新 (maxv)即可。于是我们就成功以 (O(m)) 的时间完成了对 (f) 数组计算。
故这种情况的答案即为 (f[1…m]) 中的最大值。
将两种情况的答案取 (max),就得到了整个大序列的最大子段和。
时间复杂度 (O(n*l+m))。
参考代码
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
typedef long long ll;
const int N=3e5+10,LEN=5e3+10,S=51,INF=1e18;
ll f[N],l[N],r[N],sum[N]; //以索引的顺序为序,记录以序列i为结尾的最大子段和、序列i的最大前缀和、序列i的最大后缀和、到序列i末位为止的总和
ll lq[S],rq[S],sq[S],mq[S]; //以各序列的输入顺序为序,记录序列i的最大前缀和、最大后缀和、总和、最大子段和
ll a[LEN],sx[LEN],mx[LEN]; //记录当前序列的值、前缀和、最大子段和
int n,m;
int main()
{
scanf("%d%d",&n,&m);
memset(mq,-0x3f,sizeof mq);
for(int i=1,s;i<=n;i++)
{
scanf("%d",&s);
for(int j=1;j<=s;j++)scanf("%lld",&a[j]);
for(int j=1;j<=s;j++)sx[j]=sx[j-1]+a[j];
for(int j=1;j<=s;j++){mx[j]=max(mx[j-1]+a[j],a[j]);mq[i]=max(mq[i],mx[j]);}
lq[i]=a[1]; //前缀不为空
for(int j=2;j<=s;j++)lq[i]=max(lq[i],sx[j]);
rq[i]=a[n]; //后缀不为空
for(int j=s-2;j>=0;j--)rq[i]=max(rq[i],sx[s]-sx[j]);
sq[i]=sx[s];
}
ll res=-INF;
for(int i=1,x;i<=m;i++)
{
scanf("%d",&x);
l[i]=lq[x];
r[i]=rq[x];
sum[i]=sum[i-1]+sq[x];
res=max(res,mq[x]);
}
f[1]=l[1];
ll maxv=r[1]-sum[1],ans=f[1];
for(int i=2;i<=m;i++)
{
f[i]=sum[i-1]+l[i]+maxv;
maxv=max(maxv,r[i]-sum[i]);
ans=max(ans,f[i]);
}
printf("%lld
",max(ans,res));
return 0;
}