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;
}
posted @ 2020-10-24 14:43  魑吻丶殇之玖梦  阅读(101)  评论(0编辑  收藏  举报