CF75D Big Maximum Sum
题意简述
给定 \(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\n",max(ans,res));
return 0;
}