【题解】 量化交易2

题目描述

【题目描述】
  applepi训练了一个可以自动在股票市场进行量化交易的模型。
  通常来说,applepi写出的模型,你懂得,就好比一架印钞机。不过为了谨慎起见,applepi还是想先检查一下模型的效果。
  applpie收集了“塞帕思股份(surpass)”在最近的连续N天内的价格。在每一天中,他可以做如下事情之一:
    1. 睡(把)觉(妹)。
    2. 以当天的价格作为成交价买入1股“塞帕思”的股票。
    3. 以当天的价格作为成交价卖出1股“塞帕思”的股票。
  最初applepi不持有该股票。现在你需要计算出在最优策略下,N天后applepi能够获得的最大利润。
  为了维护森林的和平,本着清仓甩锅的原则,在N天的交易结束后applepi也不能持有“塞帕思”的股票。
  此外,为了防止垄断,市场管理部门规定,如果applepi手中已经持有股票,那么他当前就不能再购买股票。
  换句话说,applepi手中最多只能持有1股“塞帕思”的股票。
【输入格式】
  每个测试点包含若干组数据,以EOF结尾。对于每组数据:
  第一行1个整数N。
  第二行N个正整数,相邻两个整数之间用1个空格隔开,表示每一天股票的价格。
【输出格式】
  对于每组数据,首先按样例所示的格式“Case #k:”输出该组数据的编号,然后输出一个整数,表示applepi最大能够获得的利润。
【样例】
  样例输入1
    6
    2 6 7 3 5 6
    8
    1 2 3 4 5 6 7 8
  样例输出1
    Case #1: 8
    Case #2: 7
  样例输入2
    10
    15831 47573 60015 51368 32460 34125 43074 75172 54014 93578
  样例输出2
    Case #1: 126460
【数据规模与约定】
  对于50%的数据,1≤N≤1000。
  对于100%的数据,1≤N≤100000,股票价格不超过100000,每个测试点至多包含5组数据。

思路

根据题意判断,这是最优解问题,而最优解问题最常用的算法就是贪心和DP。但显而易见,要求的应该是全局最优解,所以应当用DP解决更为合理。那么,这道题就可以用线性DP来解决。

根据线性DP的解题方法
首先分析阶段性。这道题的阶段性就是第 \(i\) 天。
然后定义状态。本人的第一感觉就是将状态定义为:\(dp_i\) 表示前 \(i\) 天过后可以获得的最大利润。
接着根据状态,推导状态转移方程:

  • 如果第 \(i\) 天不进行买卖,则利润不变,即 \(dp_i = dp_{i - 1}\)
  • 如果第 \(j\) 天买入,第 \(i\) 天卖出,其中 \(i < j\)。则可产生的利润为 \(a_i - a_j\) ,再加上前 \(j-1\) 天的利润,即 $dp_{j-1} $。那么总利润为 \(dp_{j-1} + a_i - a_j\)

综上所述,状态转移方程为:$$dp_i = dp_i - 1$$ $$dp_i = \max_{0 < j < i}(dp_i, dp_{j-1} + a_i - a_j)$$
其中 \(1 \le i \le n\)
最后定义初值:\(dp = -\infty, dp_0 = 0\)
那么,最终的目标利润为:\(dp_n\)
根据思考写出代码:

#include <bits/stdc++.h>
using namespace std;

const int N = 1e5 + 5;
int n, a[N];
int dp[N];

int main()
{
    int t = 0;
    while (~scanf("%d", &n))
    {
        printf("Case #%d: ", ++ t );
        for (int i = 1; i <= n; i ++ ) scanf("%d", &a[i]);

        memset(dp, 128, sizeof dp);
        dp[0] = 0;
        for (int i = 1; i <= n; i ++ )
        {
            dp[i] = dp[i - 1];
            for (int j = 1; j < i; j ++ )
                dp[i] = max(dp[i], dp[j - 1] + (a[i] - a[j]));
        }
        printf("%d\n", dp[n]);
    }

    return 0;
}

提交后,喜提超时50分。

以上代码的时间复杂度达到了 \(O(n^2)\)。而题目中给出的 \(n \le 10^5\),所以最坏的时间复杂度可达到 \(O(10^5 \times 10^5) = O(10^{10})\),直接喜提超时分。
要从根本上解决超时的问题,就要从状态的定义开始,状态的定义过于大了,还可以细分。所以,状态可以重新定义为:\(dp_{i,0}\) 表示前 \(i\) 天过后手上没有股票的最大利润,\(dp_{i,1}\) 表示前 \(i\) 天过后手上有股票的最大利润。
那么就可以分析状态转移方程:如果第 \(i\) 天过后手上没有股票,可以分为两种情况:一是,前 \(i-1\) 天过后手上没有股票,并且第 \(i\) 天不做买卖操作,即 \(dp_{i, 0} = dp_{i-1, 0}\);二是,前 \(i-1\) 天过后手上有一股股票,并在第 \(i\) 天卖出,即 \(dp_{i,0} = dp_{i-1, 1} + a_i\)。如果第 \(i\) 天过后手上有一股股票,同理,也可以分为两种情况:一是,前 \(i-1\) 天过后手上有股票,并且第 \(i\) 天不做买卖操作,即 \(dp_{i, 1} = dp_{i-1, 1}\);二是,前 \(i-1\) 天过后手上没有股票,并在第 \(i\) 天买入,即 \(dp_{i,0} = dp_{i-1, 1} - a_i\)。综上所述,状态转移方程为:$$dp_{i, 0} = \max(dp_{i-1, 0}, dp_{i-1, 1}+a_i)$$ $$dp_{i, 1} = \max(dp_{i-1, 1}, dp_{i-1, 0} - a_i)$$
其中 \(1 \le i \le n\)
最后定义初值:\(dp = -\infty, dp_{0, 0} = 0\)
那么,最终的目标利润为:\(dp_{n, 0}\)
由于 \(dp_{i, 0}\)\(dp_{i, 1}\) 都只需要从 \(dp_{i-1, 0}\)\(dp_{i-1, 1}\) 推导而来,所以时间复杂度优化到了 \(O(n)\),最坏时间复杂度仅有 \(O(10^5)\),不会超时。

代码

#include <bits/stdc++.h>
using namespace std;

const int N = 1e5 + 5;
int n, a[N];
int dp[N][2];

int main()
{
    int t = 0;
    while (~scanf("%d", &n))
    {
        printf("Case #%d: ", ++ t );
        for (int i = 1; i <= n; i ++ ) scanf("%d", &a[i]);

        memset(dp, 128, sizeof dp) ;
        dp[0][0] = 0;
        for (int i = 1; i <= n; i ++ )
        {
            dp[i][0] = max(dp[i - 1][0], dp[i - 1][1] + a[i]);
            dp[i][1] = max(dp[i - 1][1], dp[i - 1][0] - a[i]);
        }
        printf("%d\n", dp[n][0]);
    }

    return 0;
}
posted @ 2024-04-30 18:10  T_泓  阅读(16)  评论(0编辑  收藏  举报