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[i−1]>0,则f[i]=f[i−1]+a[i]。②、f[i−1]<=0,则f[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]max,那么a[pos]即右端点,问题在于求左端点。
可
以
从
p
o
s
位
置
倒
推
,
直
到
第
一
个
f
[
i
]
<
0
,
这
个
位
置
就
是
左
端
点
的
左
边
第
一
个
点
,
此
时
p
o
s
+
1
就
是
左
端
点
。
可以从pos位置倒推,直到第一个f[i]<0,这个位置就是左端点的左边第一个点,此时pos+1就是左端点。
可以从pos位置倒推,直到第一个f[i]<0,这个位置就是左端点的左边第一个点,此时pos+1就是左端点。
因 为 我 们 求 区 间 和 的 时 候 , 只 要 f [ i − 1 ] > 0 成 立 , 那 么 最 大 值 就 会 增 加 , 所 以 找 到 第 一 个 f [ i ] < 0 的 点 , 这 个 点 右 侧 第 一 个 点 即 左 端 点 。 因为我们求区间和的时候,只要f[i-1]>0成立,那么最大值就会增加,\\所以找到第一个f[i]<0的点,这个点右侧第一个点即左端点。 因为我们求区间和的时候,只要f[i−1]>0成立,那么最大值就会增加,所以找到第一个f[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)=max1≤s1≤t1≤s2≤t2≤n∑i=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,f2,f1[i]:区间[1,i]的最大区间和,f2[i]:区间[i,n]的最大区间和。最终答案应当是max(f1[i]+f2[i+1]),i∈[1,n−1]。
注意:
①
、
边
界
问
题
,
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;
}