股票买卖类线性Dp
# | 标题 | 通过率 | 难度 | |
---|---|---|---|---|
83 | 股票的最大利润 | 55.66% | 简单 | |
332 | 股票交易 | 55.29% | 简单 | |
1054 | 股票买卖 | 63.55% | 简单 | |
1055 | 股票买卖 II | 75.60% | 简单 | |
1056 | 股票买卖 III | 57.16% | 简单 | |
1057 | 股票买卖 IV | 51.54% | 中等 | |
1058 | 股票买卖 V | 66.37% | 中等 | |
1059 | 股票买卖 VI | 74.71% | 中等 | |
314 | 低买 | 57.26% | 中等 |
股票的最大利润
注意本题不买也可以,而且有可能nums.size()<2
;
class Solution {
public:
int maxDiff(vector<int>& nums) {
if(nums.size()<2) return 0;
int cmin=nums[0],ans=0;
for(int i=1;i<(int)nums.size();i++) {
ans=max(ans,nums[i]-cmin);
cmin=min(cmin,nums[i]);
}
return ans;
}
};
股票交易
推一波式子,然后单调队列优化即可。
#include<cstdio>
#include<cstring>
#include<iostream>
using namespace std;
typedef long long LL;
typedef unsigned long long ULL;
const int N=2000+5;
int n,m,w;
int ap[N],bp[N],as[N],bs[N];
int f[N][N];
// f[i][j] 表示第i天有操作并且操作完后剩下j支股票的最大收益。
int g[N],q[N];
void relax(int &a,const int &b) { a=((a>b) ? a : b); }
int main()
{
// freopen("1.in","r",stdin);
int i,j,k;
scanf("%d%d%d",&n,&m,&w);
for(i=1;i<=n;i++)
scanf("%d%d%d%d",&ap[i],&bp[i],&as[i],&bs[i]);
memset(f,-0x3f,sizeof f);
memset(g,-0x3f,sizeof g);
g[0]=0;
for(i=0;i<=n;i++) f[i][0]=0;
int hh,tt;
for(i=1;i<=n;i++) {
// 把 i-w-1 天的决策加入集合
k=i-w-1;
if(k>0) {
for(j=0;j<=m;j++)
g[j]=max(g[j],f[k][j]);
}
// buy
hh=tt=0,q[hh]=0;
for(j=1;j<=m;j++) {
while(hh<=tt&&q[hh]<j-as[i]) hh++;
relax(f[i][j],g[q[hh]]+q[hh]*ap[i]-j*ap[i]);
while(hh<=tt&&g[q[tt]]+q[tt]*ap[i]<=g[j]+j*ap[i]) tt--;
q[++tt]=j;
}
// sell
hh=tt=0,q[hh]=m;
for(j=m-1;j>=0;j--) {
while(hh<=tt&&q[hh]>j+bs[i]) hh++;
relax(f[i][j],g[q[hh]]+q[hh]*bp[i]-j*bp[i]);
while(hh<=tt&&g[q[tt]]+q[tt]*bp[i]<=g[j]+j*bp[i]) tt--;
q[++tt]=j;
}
// for(j=0;j<=m;j++) {
// for(r=max(j-as[i],0);r<j;r++) relax(f[i][j],g[r]+(r-j)*ap[i]);
// for(r=j+1;r<=min(m,j+bs[i]);r++) relax(f[i][j],g[r]+(r-j)*bp[i]);
// }
// printf("f[%d][%d] = %d\n",i,j,f[i][j]);
}
int ans=0;
for(i=0;i<=n;i++)
relax(ans,f[i][0]);
printf("%d\n",ans);
// printf("%d\n",f[n][0]);
return 0;
}
股票买卖
#include<cstdio>
#include<iostream>
typedef long long LL;
typedef unsigned long long ULL;
using namespace std;
const int INF=(-1u)>>1;
const int N=1e5+5;
int a[N];
int n;
int main()
{
// freopen("1.in","r",stdin);
int i;
int ans=0,cmin;
scanf("%d",&n);
for(i=1;i<=n;i++)
scanf("%d",&a[i]);
cmin=a[1];
for(i=2;i<=n;i++) {
ans=max(ans,a[i]-cmin);
cmin=min(cmin,a[i]);
}
cout<<ans;
return 0;
}
股票买卖 II
#include<cstdio>
#include<iostream>
typedef long long LL;
typedef unsigned long long ULL;
using namespace std;
const int N=1e5+5;
LL a[N],f[N],cmax;
int n;
int main()
{
// freopen("1.in","r",stdin);
int i;
scanf("%d",&n);
for(i=1;i<=n;i++)
scanf("%lld",&a[i]);
cmax=-a[1];
for(i=2;i<=n;i++) {
f[i]=max(f[i-1],a[i]+cmax);
cmax=max(cmax,f[i-1]-a[i]);
}
// for(i=1;i<=n;i++)
// cout<<f[i]<<" ";
cout<<f[n];
return 0;
}
股票买卖 III
#include<cstdio>
#include<iostream>
typedef long long LL;
typedef unsigned long long ULL;
using namespace std;
const int N=1e5+5;
int f[N],g[N],a[N];
int n;
int main()
{
// freopen("1.in","r",stdin);
int i;
scanf("%d",&n);
for(i=1;i<=n;i++)
scanf("%d",&a[i]);
int cmin=a[1];
for(i=2;i<=n;i++) {
f[i]=max(f[i-1],a[i]-cmin);
cmin=min(a[i],cmin);
}
int cmax=a[n];
for(i=n-1;i>=1;i--) {
g[i]=max(g[i+1],cmax-a[i]);
cmax=max(cmax,a[i]);
}
int ans=0;
for(i=2;i<=n-1;i++)
ans=max(ans,f[i]+g[i]);
cout<<ans;
return 0;
}
注意到 \(dalao\) 有跟我不一样的解法: https://www.acwing.com/solution/content/5049/
股票买卖 IV
#include<cstdio>
#include<iostream>
typedef long long LL;
typedef unsigned long long ULL;
using namespace std;
const int N=1e5+5,K=105;
int f[N][K],a[N],cmax[K];
int n,m;
// f[i][k] 表示选完了第i天的股票,所得到的最大利润,(第i天可选可不选) ;
// f[i][k]=max(f[i-1][k],f[j][k-1]+a[i]-a[j]), 0<j<i;
int main()
{
// freopen("1.in","r",stdin);
int i,k;
scanf("%d%d",&n,&m);
for(i=1;i<=n;i++)
scanf("%d",&a[i]);
fill(cmax,cmax+m+1,-a[1]);
for(i=2;i<=n;i++) {
for(k=m;k>=1;k--) {
f[i][k]=max(f[i-1][k],a[i]+cmax[k-1]);
cmax[k]=max(cmax[k],f[i][k]-a[i]);
}
cmax[0]=max(cmax[0],-a[i]);
}
int ans=0;
for(i=1;i<=n;i++)
for(k=0;k<=m;k++)
ans=max(ans,f[i][k]);//,cout<<i<<" "<<k<<":"<<f[i][k]<<endl;
cout<<ans;
return 0;
}
转移方程:\(f[i][k]=max(f[i-1][k],f[j][k-1]+a[i]-a[j]),j \in [1,i-1]\)
考虑到:\(f[j][k-1]-a[j],j \in [1,i-1]\) 与 \(i\) 无关。
令 \(cmax[k]=max(f[j][k]-a[j]),j \in [0,i-1]\) .
\(f[i][k]=max(f[i-1][k],cmax[k]+a[i])\);
动态维护 \(cmax[]\) 即可 。
注意:
- 倒序:可以把 \(cmax\) 降成一维。
- 另外维护
cmax[0]
;
另一种解法:https://www.acwing.com/solution/content/5055/
股票买卖 V
#include<bits/stdc++.h>
using namespace std;
const int N=1e5+5;
int f[N][2][2],a[N];
int n;
int main()
{
// freopen("1.in","r",stdin);
int i;
scanf("%d",&n);
f[0][1][1]=f[0][1][0]=f[0][0][1]=-1e9;
for(i=1;i<=n;i++)
scanf("%d",&a[i]);
for(i=1;i<=n;i++) {
f[i][1][1]=f[i-1][0][0]-a[i];
f[i][0][1]=max(f[i-1][1][1],f[i-1][1][0])+a[i];
f[i][0][0]=max(f[i-1][0][0],f[i-1][0][1]);
f[i][1][0]=max(f[i-1][1][0],f[i-1][1][1]);
}
cout<<max(max(f[n][1][1],f[n][0][0]),max(f[n][1][0],f[n][0][1]));
return 0;
}
股票买卖 VI
#ifdef cjlworld
f[i] ---> 第i天能获得的最大利润
f[i] = max ---> f[i-1] // 什么事都不干
---> f[j]+a[i]-a[j]-f , 1<=j<=i-1; // j买 i卖
#endif
#include<cstdio>
#include<iostream>
typedef long long LL;
typedef unsigned long long ULL;
using namespace std;
const int N=1e5+5;
int a[N],w;
int n;
int f[N],cmax;
int main()
{
// freopen("1.in","r",stdin);
int i;
scanf("%d%d",&n,&w);
for(i=1;i<=n;i++)
scanf("%d",&a[i]);
cmax=-a[1]-w;
for(i=2;i<=n;i++) {
f[i]=max(f[i-1],a[i]+cmax);
cmax=max(cmax,f[i]-a[i]-w);
}
cout<<f[n];
return 0;
}
低买
最长下降子序列 以及 最长下降子序列计数。
严格来讲不算是股票买卖的题目。
Solution:
-
\(f[i]\) 代表以 \(i\) 结尾的最长下降子序列长度。
-
\(g[i]\) 代表以 \(i\) 结尾的最长下降子序列个数。(具体而言,就是 \(f[i]\) 能由几个 \(f[j]\) 转移而来);
-
\(f[i]\) 就不讲了。对于一般的题目 \(g[i]= \sum_{j=0}^{i-1} [a[j]>a[i]\&\&f[j]+1==f[i]]*g[j]\);
-
然而: 如果两种方案的买入日序列不同,但是价格序列相同,则认为这是相同的方案(只计算一次)。
-
显然需要改变,对于相同的 \(a[j1]\) 和 \(a[j2]\) (假设 \(f[i]\) 都可以通过 \(f[j1]\) \(f[j2]\) 转移得来);
-
取 \(j1\) , \(j2\) 是有交集的。
-
但是对于同一个值中可转移的方案取 \(j\) 的最大值显然就包含的其他的方案。
-
因为其他方案可以通过只将最后一位换成 \(j\) 变成 \(j\) 的方案。
-
倒序。
Code:
#include<set>
#include<cstdio>
#include<iostream>
typedef long long LL;
typedef unsigned long long ULL;
using namespace std;
const int N=5000+5;
int a[N],f[N];
LL g[N];
int n;
set<int> S;
int main()
{
// freopen("1.in","r",stdin);
int i,j;
scanf("%d",&n);
for(i=1;i<=n;i++)
scanf("%d",&a[i]);
a[0]=1e9+5;
for(i=1;i<=n;i++) {
for(j=0;j<=i-1;j++)
if(a[j]>a[i])
f[i]=max(f[i],f[j]+1);
}
// for(i=1;i<=n;i++)
// cout<<f[i]<<" ";
// cout<<endl;
// for(i=2;i<=n;i++)
// f[i]=max(f[i],f[i-1]);
g[0]=1;
for(i=1;i<=n;i++) {
S.clear();
for(j=i-1;j>=0;j--) {
if(a[i]<a[j]&&f[i]==f[j]+1&&(!S.count(a[j]))) {
g[i]+=g[j];
S.insert(a[j]);
}
}
}
int ans1=0; LL ans2=0;
for(i=1;i<=n;i++)
ans1=max(ans1,f[i]);
S.clear();
for(i=n;i>=1;i--)
if(f[i]==ans1&&(!S.count(a[i])))
ans2+=g[i],S.insert(a[i]);
// cout<<f[n]<<" "<<g[n];
cout<<ans1<<" "<<ans2;
return 0;
}
时间复杂度:\(O(n^2logn)\)
另一种做法:https://www.acwing.com/solution/content/5093/
这种是一发现交集就把 \(j\) 较大的前半部分去掉。
1163. 纪念品
多支股票的股票买卖问题.
如果沿用上述状态机定义,问题不能得到良好解决。
但股票买卖问题有一个很好的性质,就是如果你把状态定义成全卖了,在后来的转移(第i天)时再判断第j天是否买入。
那么 (假设在第k天第一次买入了股票)收益为 \(a[i]-a[j]+a[j]-a[k]=a[i]-a[k]\) 等价于在第k天买,第i天卖。
因此第i天的状态只要考虑第 i-1 天的,就可以覆盖所有方案。
剩下就不难了。
#include<set>
#include<map>
#include<queue>
#include<stack>
#include<ctime>
#include<cmath>
#include<bitset>
#include<vector>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
typedef long long LL;
typedef unsigned long long ULL;
const int N=256,M=1e4+5;
int day,n,m;
int f[N]; // f[i] 表示在第i天,把所有纪念品都卖光的所获得的最大收入。
int a[N][N]; // a[i][j] 表示第 i 天第 j 种纪念品的价格。
int g[M];
int main()
{
// freopen("1.in","r",stdin);
int i,j,k;
int x,y;
cin>>day>>n>>m;
for(i=1;i<=day;i++)
for(j=1;j<=n;j++)
cin>>a[i][j];
f[1]=m;
for(i=2;i<=day;i++) {
f[i]=f[i-1]; // 这一天啥也不干。
memset(g,0,sizeof g); // 背包的辅助数组。
// 背包问题
for(j=1;j<=n;j++) {
if(a[i][j]>a[i-1][j]&&a[i-1][j]<=f[i-1])
x=a[i][j]-a[i-1][j],y=a[i-1][j]; // 价值和体积。
else continue;
for(k=y;k<=f[i-1];k++)
g[k]=max(g[k],g[k-y]+x);
}
f[i]=max(f[i],g[f[i-1]]+f[i-1]);
// cout<<i<<" : "<<f[i]<<endl;
}
cout<<f[day]<<endl;
return 0;
}