博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
BZOJ1492: [NOI2007]货币兑换Cash
阅读量:4501 次
发布时间:2019-06-08

本文共 4519 字,大约阅读时间需要 15 分钟。

Description

小Y最近在一家金券交易所工作。该金券交易所只发行交易两种金券:A纪念券(以下简称A券)和 B纪念券(以下简称B券)。每个持有金券的顾客都有一个自己的帐户。金券的数目可以是一个实数。每天随着市场的起伏波动,两种金券都有自己当时的价值,即每一单位金券当天可以兑换的人民币数目。我们记录第 K 天中 A券 和 B券 的价值分别为 AK 和 BK(元/单位金券)。为了方便顾客,金券交易所提供了一种非常方便的交易方式:比例交易法。比例交易法分为两个方面:(a)卖出金券:顾客提供一个 [0,100] 内的实数 OP 作为卖出比例,其意义为:将OP% 的 A券和 OP% 的 B券以当时的价值兑换为人民币;(b)买入金券:顾客支付 IP元人民币,交易所将会兑换给用户总价值为 IP的金券,并且,满足提供给顾客的A券和B券的比例在第 K 天恰好为 RateK;例如,假定接下来 3 天内的 Ak、Bk、RateK 的变化分别为:

dd(1).png-7.8kB
假定在第一天时,用户手中有 100元人民币但是没有任何金券。用户可以执行以下的操作:
dd(2).png-21.3kB
注意到,同一天内可以进行多次操作。小Y是一个很有经济头脑的员工,通过较长时间的运作和行情测算,他已经知道了未来N天内的A券和B券的价值以及Rate。他还希望能够计算出来,如果开始时拥有S元钱,那么N天后最多能够获得多少元钱。

Input

输入第一行两个正整数N、S,分别表示小Y能预知的天数以及初始时拥有的钱数。接下来N行,第K行三个实数AK、BK、RateK,意义如题目中所述。对于100%的测试数据,满足:\(0<AK≤10,0<BK≤10,0<RateK≤100,MaxProfit≤10^9\)

\(n\leq 10^5\)

【提示】

1.输入文件可能很大,请采用快速的读入方式。
2.必然存在一种最优的买卖方案满足:
每次买进操作使用完所有的人民币;每次卖出操作卖出所有的金券。

Output

只有一个实数MaxProfit,表示第N天的操作结束时能够获得的最大的金钱数目。答案保留3位小数。

Sample Input

3 1001 1 11 2 22 2 3

Sample Output

225.000

HINT

dd(3).png-21.3kB


Solution

首先题设比较复杂,不仔细读题,后面的方程肯定写不出来。

考虑到,如果有一天卖出会让你盈利,肯定是全部卖出时盈利更多。

同理,如果有一天买入会使得之后某一天卖出时赚得更大利润,那么这一天一定会全部买入。
没有上面两点这题根本不可做。

对于动归的状态,记作 \(dp[i].ma\)\(dp[i].mb\) 分别表示第 \(i\) 天如果全部买入,可以最多获得 \(dp[i].ma\)\(A\) 券,与此同时可以买到 \(dp[i].mb\)\(B\) 券。\(dp[i].ma\) 是最主要的状态。姑且把m看作质量吧,不知道该咋起名字

这样就有了 DP 方程(注意 \(i,j\) 别乱了):

\[ \begin{equation*} \begin{aligned} dp[i].mb&=\frac{val}{A[i]*rate[i]+B[i]}\\ dp[i].ma&=dp[i].mb\times rate[i]\\ val&=max\{dp[j].ma*A[i]+dp[j].mb*B[i]\} \end{aligned} \end{equation*} \]

这样得到的是一个 \(O(n^2)\) 的算法,val的最大值为最终答案

考虑对于一个决策点 \(t\),如果有两个决策方案 \(x,y\) ,那么 \(x\)\(y\) 优当且仅当:

\[ \begin{equation*} \begin{aligned} val_x&>val_y\\ dp[x].ma\times A[t]+dp[x].mb\times B[t]&>dp[y].ma\times A[t]+dp[y].mb\times B[t]\\ A[t]\times(dp[x].ma-dp[y].ma)&>B[t]\times(dp[y].mb-dp[x].mb) \end{aligned} \end{equation*} \]

这时,这已经很像斜率优化了,但是因为没有各种单调性,所以没法搞事。

我们直接令 \(dp[x].ma<dp[y].ma\) 使得其有一个单调性。
\[ \begin{equation*} \begin{aligned} \frac{A[t]}{B[t]}&<\frac{dp[y].mb-dp[x].mb}{dp[x].ma-dp[y].ma}\\ -\frac{A[t]}{B[t]}&>\frac{dp[x].mb-dp[y].mb}{dp[x].ma-dp[y].ma} \end{aligned} \end{equation*} \]
这就可以斜率优化了这是一个上凸壳

接下来,由于 \(dp[x].ma\) 实际上并不单调,所以需要用平衡树维护上凸包我没写这个

或者使用 CDQ分治

首先对于区间 \([L,R]\),可以通过 \([L,mid]\) 的信息做出来一个上凸包,用于更新 \([mid+1,R]\) 的决策,不一定是最终的最优,但要保证是 \([L,mid]\) 转移过来的最优情况。

考虑把 \(-\frac{A[t]}{B[t]}\) 看做第一维,\(dp[x].ma\) 看做第二维。
将第一维排序(升序降序都可以写出来),对第二维进行类似归并的过程。
具体过程是\(solve(l,r)\)
\(\rightarrow\)按下标进行对每一天信息的划分(小于 \(mid\) 划到 \([L,mid]\) 否则 \([mid+1,R]\)。由于之前排过序,划分后\(-\frac{A[t]}{B[t]}\)仍然有序)
\(\rightarrow solve(l,mid)\)
\(\rightarrow\)构建\([L,mid]\)凸包。进行决策,由于\(-\frac{A[t]}{B[t]}\)和凸包都是单调的,可以\(O(n)\)算一下\([mid+1,R]\)所有的最优解。
\(\rightarrow solve(mid+1,r)\)
\(\rightarrow\)按照 \(dp[x].ma\) 升序排序,这是可以进行斜率优化的基础。

这样保证了斜率优化的可行性(\(dp[x].ma\)单调递增),同时又可以不让决策的先后错乱(比如先在第三天操作再去第二天操作),是一个优秀的分治算法。时间复杂度 \(O(nlogn)\)

/**************************************************************    Problem: 1492    User: zzzc18    Language: C++    Result: Accepted    Time:1512 ms    Memory:10592 kb****************************************************************/#include
#include
#include
#include
using namespace std;const int MAXN = 100000+9;const double EPS = 1e-9;const double INF = 1e20;int n;double ans[MAXN];struct DATA{ double A,B,rate; int id;}num[MAXN];bool DATAcmp(const DATA &X,const DATA &Y){ return (-X.A/X.B)<(-Y.A/Y.B);}struct DP{ double ma,mb;//表示A的数量以及B的数量}dp[MAXN];double K(int x,int y){ if(dp[x].ma==dp[y].ma)return -INF; return (dp[x].mb-dp[y].mb)/(dp[x].ma-dp[y].ma);}bool jud(int x,int y){ if(fabs(dp[x].ma-dp[y].ma)
>1; int p1=l,p2=mid+1; for(int i=l;i<=r;i++){ if(num[i].id<=mid) tmp1[p1++]=num[i]; else tmp1[p2++]=num[i]; } for(int i=l;i<=r;i++) num[i]=tmp1[i]; CDQ(l,mid); static int top; static int sta[MAXN]; top=1; for(int i=l;i<=mid;i++){ while(top>2 && K(sta[top-1],sta[top-2])
mid;i--){ while(now
=-num[i].A/num[i].B) now++; int t=num[i].id; ans[t]=max(ans[t],dp[sta[now]].ma*num[i].A+dp[sta[now]].mb*num[i].B); } CDQ(mid+1,r); p1=l,p2=mid+1; for(int i=l;i<=r;i++){ if(p2>r || (p1<=mid && jud(p1,p2))) tmp2[i]=dp[p1++]; else tmp2[i]=dp[p2++]; } for(int i=l;i<=r;i++) dp[i]=tmp2[i];}void solve(){ sort(num+1,num+n+1,DATAcmp); CDQ(1,n); printf("%.3lf\n",ans[n]);}int main(){ scanf("%d",&n); scanf("%lf",&ans[0]); for(int i=1;i<=n;i++){ scanf("%lf%lf%lf",&num[i].A,&num[i].B,&num[i].rate); num[i].id=i; } solve(); return 0;}

转载于:https://www.cnblogs.com/zzzc18/p/8323888.html

你可能感兴趣的文章
json数组
查看>>
【转】nginx 499错误的原因及解决办法
查看>>
用C语言实现最小二乘法算法
查看>>
js学习笔记一
查看>>
[cocos2d]场景切换以及切换进度显示
查看>>
fenby C语言 P6
查看>>
【leetcode 简单】 第一百一十题 分发饼干
查看>>
解决python写入csv文件每两行间 隔一个空行的问题
查看>>
JMeter学习-019-JMeter 监听器之【聚合报告】界面字段解析及计算方法概要说明(转载)...
查看>>
C++ 通过对象方式 、指针方式两种方式去访问成员变量(属性或者方法)
查看>>
HDU 5656 CA Loves GCD 01背包+gcd
查看>>
关于Servlet周期问题
查看>>
diff文件制作
查看>>
js正则表达式验证身份证号和密码
查看>>
Windows下MySQL的my.ini文件字符集测试(二)
查看>>
Linux 命令大全
查看>>
[Database] Oracle 中的where 可以后接group by
查看>>
AsyncTask和Handler
查看>>
通过HttpClient调用服务
查看>>
请求不携带cookie问题
查看>>