DP - 最大子序列和 - PAT甲级 + Maximum sum - POJ - 2479

DP - 最大子序列和 - PAT甲级 + Maximum sum - POJ - 2479

1、最大子序列和 - PAT甲级

给定一个包含 K 个整数的序列 {N1,N2,…,NK}。

连续子序列定义为 {Ni,Ni+1,…,Nj},其中 1≤i≤j≤K。

最大子序列是指序列内各元素之和最大的连续子序列。

例如,给定序列 {−2,11,−4,13,−5,−2},它的最大子序列为 {11,−4,13},其各元素之和为 20。

现在你需要求出最大子序列的各元素之和,并且输出最大子序列的第一个元素和最后一个元素的值。

输入格式
第一行包含一个整数 K。

第二行包含 K 个整数。

输出格式
输出一行三个整数,分别表示最大子序列的各元素之和以及最大子序列的第一个元素和最后一个元素的值。

设最大子序列为 {Ni,Ni+1,…,Nj},如果答案不唯一,则选择 i 更小的解,如果仍不唯一,则选择 j 更小的解。

注意,我们规定,如果所有 K 个数字均为负数,则其最大和定义为 0,并且应该输出整个序列的第一个数字和最后一个数字。

数据范围
1≤K≤10000,
序列内元素的绝对值不超过 105

输入样例:
10
-10 1 2 3 4 -5 -23 3 7 -21
输出样例:
10 1 4

分析:

状 态 表 示 : f [ i ] : 以 第 i 个 元 素 结 尾 的 最 大 子 序 列 和 。   状 态 计 算 : ① 、 f [ i − 1 ] > 0 , 则 f [ i ] = f [ i − 1 ] + a [ i ] 。 ② 、 f [ i − 1 ] < = 0 , 则 f [ i ] = a [ i ] 。 状态表示:f[i]:以第i个元素结尾的最大子序列和。\\ \ \\ 状态计算:\\①、f[i-1]>0,则f[i]=f[i-1]+a[i]。\\②、f[i-1]<=0,则f[i]=a[i]。 f[i]:i f[i1]>0f[i]=f[i1]+a[i]f[i1]<=0f[i]=a[i]

最 终 结 果 r e s = m a x ( f [ i ] ) , i ∈ [ 1 , n ] 。 最终结果res=max(f[i]),i∈[1,n]。 res=max(f[i])i[1,n]

区 间 端 点 的 计 算 : 首 先 对 于 p o s = i , 使 得 f [ i ] m a x , 那 么 a [ p o s ] 即 右 端 点 , 问 题 在 于 求 左 端 点 。 区间端点的计算:首先对于pos=i,使得f[i]_{max},那么a[pos]即右端点,问题在于求左端点。 pos=i使f[i]maxa[pos]
可 以 从 p o s 位 置 倒 推 , 直 到 第 一 个 f [ i ] < 0 , 这 个 位 置 就 是 左 端 点 的 左 边 第 一 个 点 , 此 时 p o s + 1 就 是 左 端 点 。 可以从pos位置倒推,直到第一个f[i]<0,这个位置就是左端点的左边第一个点,此时pos+1就是左端点。 posf[i]<0pos+1

因 为 我 们 求 区 间 和 的 时 候 , 只 要 f [ i − 1 ] > 0 成 立 , 那 么 最 大 值 就 会 增 加 , 所 以 找 到 第 一 个 f [ i ] < 0 的 点 , 这 个 点 右 侧 第 一 个 点 即 左 端 点 。 因为我们求区间和的时候,只要f[i-1]>0成立,那么最大值就会增加,\\所以找到第一个f[i]<0的点,这个点右侧第一个点即左端点。 f[i1]>0f[i]<0

代码:

#include<cstdio>
#include<iostream>
#include<algorithm>
#define inf 0x3f3f3f3f

using namespace std;

const int N=10010;

int n,a[N],f[N],cnt;

int main()
{
    scanf("%d",&n);
    for(int i=1;i<=n;i++) {scanf("%d",&a[i]);if(a[i]<0) cnt++;}
    
    if(cnt==n) {cout<<0<<' '<<a[1]<<' '<<a[n]<<endl;return 0;}
    
    for(int i=1;i<=n;i++)
        f[i]=max(f[i-1],0)+a[i];
    
    int res=-inf,pos=0;;
    for(int i=1;i<=n;i++) 
        if(res<f[i]) 
        {
            res=f[i];
            pos=i;
        }
    
    int r=pos;
    while(pos&&f[pos]>=0) pos--;
    
    printf("%d %d %d\n",res,a[pos+1],a[r]);
    
    return 0; 
}

2、Maximum sum - POJ - 2479

对于给定的整数序列 A={a1,a2,…,an},找出两个不重合连续子段,使得两子段中所有数字的和最大。

即 求 : d ( A ) = m a x 1 ≤ s 1 ≤ t 1 ≤ s 2 ≤ t 2 ≤ n ∑ i = s 1 t 1 a i + ∑ j = s 2 t 2 a j 即求:d(A)=max_{1≤s1≤t1≤s2≤t2≤n}{∑_{i=s1}^{t_1}a_i+∑_{j=s_2}^{t_2}a_j} d(A)=max1s1t1s2t2ni=s1t1ai+j=s2t2aj

输入格式
第一行是一个整数 T,代表一共有多少组数据。

接下来是 T 组数据。

每组数据的第一行是一个整数,代表数据个数据 n,第二行是 n 个整数 a1,a2,…,an。

输出格式
每组数据输出一个整数,占一行,就是 d(A) 的值。

数据范围
1≤T≤30,
2≤n≤50000,
|ai|≤10000

输入样例:
1
10
1 -1 2 2 3 -3 4 -4 5 -5
输出样例:
13

样例解释
在样例中,我们取{2,2,3,-3,4}和{5}两个子段,即可得到答案。


题解:

同 求 最 大 连 续 区 间 和 , 只 不 过 是 求 2 个 子 区 间 的 最 大 区 间 和 。 同求最大连续区间和,只不过是求2个子区间的最大区间和。 2

类 似 于 前 缀 和 的 思 想 : 两 个 数 组 f 1 , f 2 , f 1 [ i ] : 区 间 [ 1 , i ] 的 最 大 区 间 和 , f 2 [ i ] : 区 间 [ i , n ] 的 最 大 区 间 和 。 最 终 答 案 应 当 是 m a x ( f 1 [ i ] + f 2 [ i + 1 ] ) , i ∈ [ 1 , n − 1 ] 。 类似于前缀和的思想:\\两个数组f_1,f_2,f_1[i]:区间[1,i]的最大区间和,f_2[i]:区间[i,n]的最大区间和。\\最终答案应当是max(f_1[i]+f_2[i+1]),i∈[1,n-1]。 f1,f2f1[i][1,i]f2[i][i,n]max(f1[i]+f2[i+1])i[1,n1]

注意:
① 、 边 界 问 题 , a [ i ] 可 能 是 负 数 , 所 以 要 注 意 f [ 0 ] = 0 。 ② 、 整 型 溢 出 问 题 。 ①、边界问题,a[i]可能是负数,所以要注意f[0]=0。\\②、整型溢出问题。 a[i]f[0]=0

代码:

#include<iostream>
#include<cstdio>
#include<algorithm>
#define ll long long

using namespace std;
const int N=5e4+10;

int a[N],t,n,i;
ll f1[N],f2[N];

int main()
{
	cin>>t;
	while(t--)
	{
		scanf("%d",&n);
		for(i=1;i<=n;++i)
			scanf("%d",&a[i]);

		f1[1]=a[1];
		for(i=2;i<=n;++i) f1[i]=max(f1[i-1],(ll)0)+a[i];
		for(i=2;i<=n;++i) f1[i]=max(f1[i],f1[i-1]);

		f2[n]=a[n];
		for(i=n-1;i>=1;--i) f2[i]=max(f2[i+1],(ll)0)+a[i];
		for(i=n-1;i>=1;--i) f2[i]=max(f2[i],f2[i+1]);

		ll ans=-10000*N;
		for(i=1;i<n;++i)
			ans=max(ans,f1[i]+f2[i+1]);
		printf("%lld\n",ans);
	}
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值