【题目链接】
ybt 1302:股票买卖
OpenJudge NOI 2.6 8464:股票买卖
【题目考点】
1. 动态规划:线性动规
【解题思路】
解法1:
该题可以抽象为:在一个长为n的数字序列上,第i个数字为a[i]
。找满足
1
≤
i
1
≤
j
1
≤
i
2
≤
j
2
≤
n
1\le i1 \le j1 \le i2 \le j2 \le n
1≤i1≤j1≤i2≤j2≤n的两个数对(i1, j1), (i2, j2),使得a[j1]-a[i1]+a[j2]-a[i2]
最大。
还可以简单描述为:定义区间差值为区间两端点数值的差,求一个数字序列上两个不相交的区间差值加和的最大值。(区间长度大于等于1)
该题可以用与1305:Maximum sum类似的思路来做。
- 定义数组de,
de[i]
指以a[i]
为结尾的所有区间中,区间差值的最大的区间的区间差值。自然de[i]
为当前数字a[i]
减去第1到第i数字中的最小值。记mn为a[1]~a[i]
中的最小值,那么de[i] = a[i] - mn
。 - 类似地,定义数组db,
db[i]
指以a[i]
为起始的所有区间中,区间差值的最大的区间的区间差值。自然db[i]
为第i到第n数字中的最大值减去a[i]
,记mx为a[i]~a[n]
中的最大值,那么db[i] = mx - a[i]
1. 状态定义
集合:数字序列中的区间
限制:关注的数字范围
属性:区间差值
条件:最大
统计量:区间差值
状态定义:
dpe[i]
:第1到第i个数字构成的所有区间中,区间差值最大的区间的区间差值。
dpb[i]
:第i到第n个数字构成的所有区间中,区间差值最大的区间的区间差值。
- 以第1数字到第i数字为结尾的所有区间中区间差值的最大值,即为第1到第i个数字构成的所有区间中区间差值的最大值,即
dpe[i]
为de[1]~de[i]
中的最大值。
所以dpe[i]
为de[1]~de[i-1]
中的最大值dpe[i-1]
与de[i]
相比的较大值,即dpe[i] = max(dpe[i-1], de[i])
- 以第i数字到第n数字为起始的所有区间中区间差值的最大值,即为第i到第n个数字构成的所有区间中区间差值的最大值,即
dpb[i]
为db[i]~db[n]
中的最大值
所以dpb[i]
为db[i+1]~db[n]
中的最大值dpb[i+1]
与db[i]
相比的较大值,即dpb[i] = max(dpb[i+1], db[i])
2. 状态转移方程
集合:第1到第n个数字构成的所有区间
分割集合:枚举分割点,分成第1到第i个数字构成的区间,与第i到第n个数字构成的区间
i从1循环到n-1,取a[1]~a[i]
中区间差值的最大值dpe[i]
。以及a[i+1]~a[n]
中区间差值的最大值dpb[i+1]
。加和为ans = dpe[i]+dpb[i+1]
。对于每个i求出的ans,求最大值,得到的结果就是整个序列中两个不相交的区间的区间差值加和的最大值,即为原问题中两次买卖得到的最大利润。
解法2:
该问题也可以不求dpe与dpb,只用de与db。直接用第1到第i数字中的最大区间差值mxde(相当于dpe[i]
)加上以i+1为起始的区间的最大区间差值(db[i]
),求这个数字的最大值。结果是一样的。
【题解代码】
解法1:
- 基本解法
#include<bits/stdc++.h>
using namespace std;
#define N 100005
#define INF 0x3f3f3f3f
int t, n, ans, a[N], de[N], db[N], dpe[N], dpb[N];
int main()
{
int mn, mx;
scanf("%d", &t);
while(t--)
{
memset(dpe, 0, sizeof(dpe));
memset(dpb, 0, sizeof(dpb));
scanf("%d", &n);
for(int i = 1; i <= n; ++i)
scanf("%d", &a[i]);
mn = a[1];
for(int i = 1; i <= n; ++i)
{
mn = min(mn, a[i]);
de[i] = a[i] - mn;//以a[i]为结尾的所有区间中,区间差值的最大的区间的区间差值。
dpe[i] = max(dpe[i-1], de[i]);//第1到第i个数字构成的所有区间中,区间差值最大的区间的区间差值。
}
mx = a[n];
for(int i = n; i >= 1; --i)
{
mx = max(mx, a[i]);
db[i] = mx - a[i];//以a[i]为起始的所有区间中,区间差值的最大的区间的区间差值。
dpb[i] = max(dpb[i+1], db[i]);//第i到第n个数字构成的所有区间中,区间差值最大的区间的区间差值。
}
ans = -INF;//原序列中不相交的两个区间的区间差值加和的最大值
for(int i = 1; i < n; ++i)//枚举分割点
ans = max(ans, dpe[i]+dpb[i+1]);
printf("%d\n", ans);
}
return 0;
}
- 优化:变递推为迭代
de与db可以降维,从数组变为一个变量,而后发现这是个中间变量,可以删去。
#include<bits/stdc++.h>
using namespace std;
#define N 100005
#define INF 0x3f3f3f3f
int t, n, ans, mn, mx, a[N], dpe[N], dpb[N];
int main()
{
scanf("%d", &t);
while(t--)
{
memset(dpe, 0, sizeof(dpe));
memset(dpb, 0, sizeof(dpb));
scanf("%d", &n);
for(int i = 1; i <= n; ++i)
scanf("%d", &a[i]);
mn = a[1];
for(int i = 1; i <= n; ++i)
{
mn = min(mn, a[i]);
dpe[i] = max(dpe[i-1], a[i] - mn);//第1到第i个数字构成的所有区间中,区间差值最大的区间的区间差值。
}
mx = a[n];
for(int i = n; i >= 1; --i)
{
mx = max(mx, a[i]);
dpb[i] = max(dpb[i+1], mx - a[i]);//第i到第n个数字构成的所有区间中,区间差值最大的区间的区间差值。
}
ans = -INF;//原序列中不相交的两个区间的区间差值加和的最大值
for(int i = 1; i < n; ++i)//枚举分割点
ans = max(ans, dpe[i]+dpb[i+1]);
printf("%d\n", ans);
}
return 0;
}
- 解法2:
#include<bits/stdc++.h>
using namespace std;
#define N 100005
#define INF 0x3f3f3f3f
int t, n, mn, mx, mxde, ans, a[N], de[N], db[N];
int main()
{
scanf("%d", &t);
while(t--)
{
scanf("%d", &n);
for(int i = 1; i <= n; ++i)
scanf("%d", &a[i]);
mn = a[1];
for(int i = 1; i <= n; ++i)
{
mn = min(mn, a[i]);
de[i] = a[i] - mn;//以a[i]为结尾的所有区间中,区间差值的最大的区间的区间差值。
}
mx = a[n];
for(int i = n; i >= 1; --i)
{
mx = max(mx, a[i]);
db[i] = mx - a[i];//以a[i]为起始的所有区间中,区间差值的最大的区间的区间差值。
}
mxde = ans = -INF;//ans:原序列中不相交的两个区间的区间差值加和的最大值
for(int i = 1; i < n; ++i)//枚举分割点
{
mxde = max(mxde, de[i]);//mxde为第1到第i个数字构成的所有区间中,区间差值最大的区间的区间差值。
ans = max(ans, mxde + db[i+1]);
}
printf("%d\n", ans);
}
return 0;
}