bzoj 2470: [2010中山市选]股票投资 dp

Description

很多人都梦想成为股神,但是股票投资是需要一定基本功的。熟知股票买卖的规则就是其中一件很重要的事情。顾名思义,股票的单位是“股”;但是实际上,在公开交易市场,股票交易的单位是“手”,1手股票等于100股股票。另外,和其他商业买卖一样,股票交易是要纳税的。股票交易的税有两种,一种是交易税,一种是印花税。假设当前股价是P,你要买或者卖X手股票,那么你需要付出或者得到的钱是G=P*100*X。与此同时,无论买或者卖,你都必须支付交易税和印花税。印花税有一个固定税率T,例如0.001,则你所需支付的印花税为G*T。交易税类似印花税,也有一个固定税率S1,但同时也有一个最低税额S2,这意味着,如果G*S1<S2,则你依然需要支付S2的税款,即你需要缴纳的税款为max(G*S1,S2)。
好了,懂得这个规则,现在你可以小试牛刀了。不过,股票市场更需要的是投资策略。问题来了,假设某个时刻T0,你拥有C元现金;接下来的n秒钟,T1,T2,…,Tn,你都清楚知道某一只股票的价格Pi。在这n秒钟,你都可以任意买入或者卖出或者持有这只股票,当然,无论什么时候,你必须保证你的现金不能出现负数,你也不可能卖出比你持有数量更多的股票。同时T,S1,S2也是已知的。在这样的情况下,第n秒结束后,你可以拥有的现金最多会是多少?

Input

输入包含多组测试数据。第一行包含一个正整数T,表示测试数据数目。每组数据开头是4个实数C(0<C<=1,000,000), S1(0.0001<S1<0.01), S2(1<=S2<=100), T(0.0001<T<0.01),分别代表你T0时刻开始拥有的现金,印花税率,交易税率和最低交易税额。接下来有一个整数n(0<n<=30000),代表我们知道后n秒钟的股票价格。最后是n个实数,代表接下来n秒钟每一秒股票的价格。

Output

对每组数据,输出一行,包含一个保留3位小数的实数,表示你能获得的最多现金。答案保证不会超过1e15。
Sample Input

1

5000 0.001 5 0.003

6

20 21 20 19 20 19

Sample Output

5332.000

这里写图片描述

勉为其难的刷了道dp题……

分析:我们设状态f[i]为在i卖掉的最大值,g[i,1]为在i买的股票数,g[i,2]为剩下的钱,答案就是f[i]的最大值。

在每一个时刻下,要不花所有钱去买股票,要不卖掉全部股票。(对于x,y为两个不同的时刻,如果两个时刻都买入,且中间没有卖出,显然会在便宜的那个时刻买入,卖出反之。这就说明了不可能连续买入(卖出),也就是买了就只能卖出,不能再买。为了利益的最大化,肯定多买点,就是花掉所有钱去买,卖也多买点,那就是把所有卖掉= =)

考虑转移
f[i]=max(tax(g[j,0]*value)+g[j,1])
{0<j<i}

//tax(x)为x的税后价格,此时把j时刻的股票全卖掉
//f[i]初值为c

我们设q为前面的f[0]~f[i-1]最大值(显然要用尽量多的钱买股票)

//g[i,1]=p {tax(p*value)<=q p尽可能大}
//g[i,2]=q-tax(p*value)

我们得到了一个O(n^2)的dp,考虑优化,显然q是可以压掉平方的,即
q=max(q,f[i-1])

如何加快f[i]的转移呢?

显然对于前面一个价格value[j]>=value[i],这是没有可能转移的。
怎样判断哪个最优呢?
我们设a1=g[k,1] a2=g[k,2] b1=g[l,1] b2=g[l,2] {0<=k,l<=i-1}
x=value[i],假设a1>b1
如果k比l优,有

a1*x+a2>b1*x+b2

化简得

x>(b2-a2)/(a1-b1)

显然a2,b2为value[k],value[l]的余数,有0<=a2,b2<=value[k],value[l]<x
所以b2-a2<x,而a1>b1,且为整数,所以a1-b1>=1,所以右边恒小于x。
也就是说,当a1>a2,k一定比l优。相等显然取剩余钱最多的。

加税后,正确性显然。

复杂度:O(nT)

代码:

var
 test,n:longint;
 f:array [-1..30001] of extended;
 g:array [-1..30001,1..2] of extended;
 c,s1,s2,t,value,q,p,ct,ans,g1,g2:extended;

function max(x,y:extended):extended;
 begin
  if x>y then exit(x)
         else exit(y);
 end;

function tax(x:extended;b:longint):extended;
var sum:extended;
 begin
  if x=0 then exit(0);
  sum:=x*t;
  sum:=max(s1*x,s2)+sum;
  if b=0 then exit(x-sum)
         else exit(x+sum);
 end;
procedure main;
 var i,j:longint;
begin
 readln(c,s1,s2,t);
 f[0]:=c;
 g[0,1]:=0;
 g[0,2]:=c;
 ans:=0; q:=0;
 g1:=0; g2:=0;
 readln(n);
 for i:=1 to n do
  begin
   read(value);
   value:=value*100;
   f[i]:=0; q:=max(q,f[i-1]);
   if g[i-1,1]>g1 then
    begin
     g1:=g[i-1,1];
     g2:=g[i-1,2];
    end;
   if g[i-1,1]=g1 then
     g2:=max(g2,g[i-1,2]);
   f[i]:=max(f[i],tax(g1*value,0)+g2);
   p:=trunc(q/value);
   while tax(p*value,1)>q do p:=p-1;
   g[i,1]:=p;
   g[i,2]:=q-tax(p*value,1);
   ans:=max(ans,f[i]);
  end;
 writeln(ans:0:3);
end;

begin
 readln(test);
 while test>0 do
  begin
   main;
   test:=test-1;
  end;
end.
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值