JZOJ2199. 【中山市选2010】股票投资

32 篇文章 0 订阅
16 篇文章 0 订阅

一道看似很难的DP

很显然的一个状态就是设到i单位时间的状态
f[i]表示最大收益
g[i]表示最多获得的股票数
G[i]表示在股票数最多的情况下剩余的钱
设m[i]表示第i时间1手股票的价格

O(n^2)就不讲了
f [ i ] = m a x ( g [ j ] ∗ m [ i ] + G [ j ] − t a x ( g [ j ] ∗ m ) ) f[i]=max(g[j]*m[i]+G[j]-tax(g[j]*m)) f[i]=max(g[j]m[i]+G[j]tax(g[j]m))
g [ i ] = m a x ( f [ j ] ) g[i]=max(f[j]) g[i]=max(f[j]) (能买的股票数)
G [ i ] = m a x ( f [ j ] ) − 买 的 股 票 钱 − 税 G[i]=max(f[j])-买的股票钱-税 G[i]=max(f[j])(即剩余钱数)
其中tax(n)表示买/卖价钱为n的税价
(如果买了能更优的话肯定多买)

考虑优化。
显然max(f[j])可以直接维护,问题就是g和G的维护。
事实上可以维护最大的g,在g最大的情况下维护最大的G。


但是这样设有个问题,比如有j和k两个位置,
其中j能买的股票比k多,但是k剩余的钱比j多。
这就需要证明。

正确性证明:
因为m[j]不可能>=m[i](会亏本傻是可以的但是要有个限度
所以m[j]< m[i]

先不考虑税,如果有两个状态j,k,设j能买的股票为gj,剩余Gj元,k同理。
显然1<=m[j],m[k]< m[i]

设m表示当前价格(即m[i])
如果j比k优,那么显然要满足
g j ∗ m + G j &gt; g k ∗ m + G k gj*m+Gj&gt;gk*m+Gk gjm+Gj>gkm+Gk
化简得到
m &gt; ( G k − G j ) / ( g j − g k ) m&gt;(Gk-Gj)/(gj-gk) m>(GkGj)/(gjgk)
因为Gj、Gk都是m[j]、m[k]的余数(能买肯定多买),所以
G j , G k &lt; m [ j ] , m [ k ] &lt; m Gj,Gk&lt;m[j],m[k]&lt; m Gj,Gk<m[j],m[k]<m
G k − G j &lt; m Gk-Gj&lt;m GkGj<m

所以只要 ( g j − g k ) &gt; = 1 (gj-gk)&gt;=1 (gjgk)>=1式子就能成立。
因为gj、gk都是整数,所以只要gj> gk就一定更优。

所以只要维护最大的g,在g最大的情况下维护最大的G。
加税后显然。


那么还有一个问题,那就是g的求法。

个人最先想到的就是假设不加税的情况下求出g,然后如果不成立就g–直到成立。
然而这样复杂度玄妙

g2=floor(f/m);
while ((g2*m+tax(g2*m))>f)
g2--;
G2=f-(g2*m+tax(g2*m));

大概就是这样(小于等于是因为最终的目的是让g2合法 --10/16/2018)
( g 2 ∗ m + t a x ( g 2 ∗ m ) ) &lt; = f (g2*m+tax(g2*m))&lt;=f (g2m+tax(g2m))<=f
然后发现把tax()拆开后变成这样
( g 2 ∗ m + g 2 ∗ m ∗ t + m a x ( g 2 ∗ m ∗ s 1 , s 2 ) ) &lt; = f (g2*m+g2*m*t+max(g2*m*s1,s2))&lt;=f (g2m+g2mt+max(g2ms1,s2))<=f
那么max中有两种情况
m a x = g 2 ∗ m ∗ s 1 max=g2*m*s1 max=g2ms1
( g 2 ∗ m + g 2 ∗ m ∗ t + g 2 ∗ m ∗ s 1 ) &lt; = f (g2*m+g2*m*t+g2*m*s1)&lt;=f (g2m+g2mt+g2ms1)<=f
g 2 ∗ m ∗ ( 1 + t + s 1 ) &lt; = f g2*m*(1+t+s1)&lt;=f g2m(1+t+s1)<=f
g 2 &lt; = f / m / ( 1 + t + s 1 ) g2&lt;=f/m/(1+t+s1) g2<=f/m/(1+t+s1)
显然可以直接算。

m a x = s 2 max=s2 max=s2
( g 2 ∗ m + g 2 ∗ m ∗ t + s 2 ) &lt; = f (g2*m+g2*m*t+s2)&lt;=f (g2m+g2mt+s2)<=f
g 2 ∗ m ∗ ( 1 + t ) &lt; = f − s 2 g2*m*(1+t)&lt;=f-s2 g2m(1+t)<=fs2
g 2 &lt; = ( f − s 2 ) / m / ( 1 + t ) g2&lt;=(f-s2)/m/(1+t) g2<=(fs2)/m/(1+t)
直接算+1

因为是分类讨论,所以最后还要分别判断两种情况是否合法,之后再分别更新,这样才是最严谨的做法
(16/10/2018)

code

#include <iostream>
#include <cstdio>
#include <cmath>
#define fo(a,b,c) for (a=b; a<=c; a++)
#define fd(a,b,c) for (a=b; a>=c; a--)
#define min(x,y) (x<y?x:y)
#define max(x,y) (x>y?x:y)
using namespace std;

int T,i,j,k,l,n,g,g2;
double s1,s2,t,mf,f,G,G2,m;

double tax(double n){return (n*t+max(n*s1,s2));}

void change()
{
	G2=f-(g2*m+tax(g2*m));
	if (g2>g)
	{
		g=g2;
		G=G2;
	}
	else
	if ((g2==g) && (G2>G))
	G=G2;
}

int main()
{
	freopen("stock.in","r",stdin);
	freopen("stock.out","w",stdout);

	scanf("%d",&T);
	for (;T;T--)
	{
		scanf("%lf%lf%lf%lf",&f,&s1,&s2,&t);
		scanf("%d",&n);

		g=0;
		G=0;

		fo(i,1,n)
		{
			scanf("%lf",&m);
			m*=100;
			
			f=max(f,g*m+G-tax(g*m));
			
			g2=f/m/(t+1+s1);
			if ((g2*m+tax(g2*m))<=f && g2*m*s1>=s2) change();
			g2=(f-s2)/m/(t+1);
			if ((g2*m+tax(g2*m))<=f && s2>=g2*m*s1) change();
			
		}

		printf("%0.3lf\n",f);
	}

	fclose(stdin);
	fclose(stdout);

	return 0;
}

以下是旧代码

#include <iostream>
#include <cstdio>
#include <cmath>
#define fo(a,b,c) for (a=b; a<=c; a++)
#define fd(a,b,c) for (a=b; a>=c; a--)
#define min(x,y) (x<y?x:y)
#define max(x,y) (x>y?x:y)
using namespace std;

int T,i,j,k,l,n,g,g2;
double s1,s2,t,mf,f,G,G2,m;

double tax(double n){return (n*t+max(n*s1,s2));}

int main()
{
	freopen("stock.in","r",stdin);
	freopen("stock.out","w",stdout);
	
	scanf("%d",&T);
	for (;T;T--)
	{
		scanf("%lf%lf%lf%lf",&f,&s1,&s2,&t);
		scanf("%d",&n);
		
		g=0;
		G=0;
		
		fo(i,1,n)
		{
			scanf("%lf",&m);
			m*=100;
			
			f=max(f,g*m+G-tax(g*m));
			
			g2=f/m/(t+1+s1);
			if ((g2*m+tax(g2*m))>f)
			g2=(f-s2)/m/(t+1);
			G2=f-(g2*m+tax(g2*m));
			
			if (g2>g)
			{
				g=g2;
				G=G2;
			}
			else
			if ((g2==g) && (G2>G))
			G=G2;
		}
		
		printf("%0.3lf\n",f);
	}
	
	fclose(stdin);
	fclose(stdout);
	
	return 0;
}

更新:16/10/2018 18:14

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值