• 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
    ",max(ans,res));
    	return 0;
    }
    
  • 相关阅读:
    js的同步与异步
    单体应用SSM
    Spring 事务管理简介
    Linux
    Docker
    spring Cloud Netflix
    平台即服务
    MySQL InnoDB 索引组织表 & 主键作用
    Innodb Double Write
    Laravel 5.6: Specified key was too long error
  • 原文地址:https://www.cnblogs.com/ninedream/p/13869442.html
Copyright © 2020-2023  润新知