【BZOJ1492】【NOI2007】货币兑换(动态规划,CDQ分治,Splay)
【BZOJ1492】【NOI2007】货币兑换(动态规划,CDQ分治,Splay)
题面
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 的变化分别为:
假定在第一天时,用户手中有 100元 人民币但是没有任何金券。用户可以执行以下的操作:
注意到,同一天内可以进行多次操作。小Y是一个很有经济头脑的员工,通过较长时间的运作和行情测算,他已经
知道了未来N天内的A券和B券的价值以及Rate。他还希望能够计算出来,如果开始时拥有S元钱,那么N天后最多能
够获得多少元钱。
Input
输入第一行两个正整数N、S,分别表示小Y能预知的天数以及初始时拥有的钱数。接下来N行,第K行三个实数AK、B
K、RateK,意义如题目中所述。对于100%的测试数据,满足:0<AK≤10;0<BK≤10;0<RateK≤100;MaxProfit≤1
0^9。
【提示】
1.输入文件可能很大,请采用快速的读入方式。
2.必然存在一种最优的买卖方案满足:
每次买进操作使用完所有的人民币;
每次卖出操作卖出所有的金券。
Output
只有一个实数MaxProfit,表示第N天的操作结束时能够获得的最大的金钱数目。答案保留3位小数。
Sample Input
3 100
1 1 1
1 2 2
2 2 3
Sample Output
225.000
HINT
题解
首先考虑一下暴力吧
我们想想,肯定是一天把手上所有钱全部买了之后在后面的某一天把他们全部卖出去。
考虑一个\(dp\)
设\(f[i]\)表示第\(i\)天手上的\(A\)券的最大值
当然,记录手中的最大钱数或者\(B\)券的数量都是一样的
\(ans\)记录任意时刻手中的最多钱数
那么,
对于一个\(f[i]\)而言
\(ans=max(ans,f[j]全部在第i天卖掉)\)
\(f[i]=ans全部买掉\)
这样的\(dp\)很显然是\(O(n^2)\),\(60pts\)
#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<cmath>
#include<algorithm>
using namespace std;
#define ll long long
#define RG register
#define MAX 111111
double A[MAX],B[MAX],R[MAX];
double f[MAX],ans;
int n,S;
int main()
{
ios::sync_with_stdio(false);
cin>>n>>S;
for(int i=1;i<=n;++i)cin>>A[i]>>B[i]>>R[i];
f[1]=S*R[1]/(A[1]*R[1]+B[1]);
ans=S;
for(int i=2;i<=n;++i)
{
for(int j=1;j<i;++j)
ans=max(ans,f[j]*A[i]+f[j]/R[j]*B[i]);
f[i]=ans*R[i]/(A[i]*R[i]+B[i]);
}
printf("%.3lf\n",ans);
return 0;
}
显然在\(dp\)的时候,枚举哪一天是少不了的
现在考虑的是快速计算\(ans\)
把关于\(ans\)的式子写一下
\(ans=max(ans,f[j]*A[i]+f[j]/R[j]*B[i])\)
假设\(j\)位置的转移比\(k\)位置更优
\]
化简得到
\]
上面那坨东西好不爽
令\(g[i]=\frac{f[i]}{R[i]}\)
\]
左边这个像个斜率的形式,右边像直线的两点式
因此,对于每一天
都可以对应一个点\((f[i],g[i])\)
以及一个斜率\(-\frac{A[i]}{B[i]}\)
维护一个凸壳??
但是\(f[i]\)显然不递增,\(g[i]\)也显然不递增
没法用单调栈来维护。。。很蛋疼啊。。。
要求的东西显然是所有的直线中,斜率小于目标\(K\)
并且连接之后与\(y\)轴的截距最大
所以,维护一个上凸壳,在里面维护斜率,
同时,支持任意插点,动态维护凸壳,二分斜率等操作
看起来是个平衡树之类的东西。。。
方法一:\(CDQ\)分治
我肯定不会闲着蛋疼先去写平衡树,
所以我们先来写一个稍微好写点的\(CDQ\)分治
可以参考\(CDQ\)的论文
其实我也是照着网上题解打的
这里稍微换一下,\(f[i]\)表示的是第\(i\)天时手上的最大钱数。
其他的转移大致相同,不再提了。
当然,坐标也有一点点小小的变化
考虑如何\(CDQ\)分治
很显然,如果我要求出\(x\)位置的值
显然就要维护出\(1..x-1\)的值
所以进行\(CDQ\)分治
第一点,肯定是按照天数排序分割左右
但是同一侧内部不一定按照天数
考虑怎么计算左右之间的贡献
相当把左侧的凸壳先构造出来,右侧按照斜率排序
用一个单调指针扫过去依次更新答案就行了
因为左侧要维护凸壳
因此归并排序的时候按照\(x\)坐标排序
每次暴力把左侧的凸壳构造出来,复杂度\(O(n)\)
当只有一个点的时候,这个点的\(dp\)值就已经算出来了
同时,\(x,y\)坐标也可以算出来
所以这样的复杂度是多少???
我觉得是\(O(nlogn)\)
#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<cmath>
#include<algorithm>
using namespace std;
#define ll long long
#define RG register
#define MAX 111111
struct Node{double a,b,r,k,x,y;int id;}p[MAX],q[MAX];
bool operator<(Node a,Node b){return a.k>b.k;}
double f[MAX],ans;
int n,S[MAX],top;
double K(int x,int y)
{
if(!y||fabs(p[x].x-p[y].x)<1e-9)return -1e20;
return (p[x].y-p[y].y)/(p[x].x-p[y].x);
}
void CDQ(int l,int r)
{
int mid=(l+r)>>1;
if(l==r)
{
f[l]=max(f[l],f[l-1]);
p[l].y=f[l]/(p[l].a*p[l].r+p[l].b);
p[l].x=p[l].y*p[l].r;
return;
}
int t1=l,t2=mid+1;
for(int i=l;i<=r;++i)
if(p[i].id<=mid)q[t1++]=p[i];
else q[t2++]=p[i];
for(int i=l;i<=r;++i)p[i]=q[i];
CDQ(l,mid);top=0;
for(int i=l;i<=mid;++i)
{
while(top>1&&K(S[top-1],S[top])<K(S[top-1],i))--top;
S[++top]=i;
}
S[++top]=0;int nw=1;
for(int i=mid+1;i<=r;++i)
{
while(nw<top&&K(S[nw],S[nw+1])>p[i].k)++nw;
f[p[i].id]=max(f[p[i].id],p[i].a*p[S[nw]].x+p[i].b*p[S[nw]].y);
}
CDQ(mid+1,r);
t1=l;t2=mid+1;
for(int i=l;i<=r;++i)
if(((p[t1].x<p[t2].x)||(fabs(p[t1].x-p[t2].x)<1e-9&&p[t1].y<p[t2].y)||t2>r)&&t1<=mid)
q[i]=p[t1++];
else q[i]=p[t2++];
for(int i=l;i<=r;++i)p[i]=q[i];
}
int main()
{
ios::sync_with_stdio(false);
cin>>n>>f[0];
for(int i=1;i<=n;++i)
{
cin>>p[i].a>>p[i].b>>p[i].r;
p[i].k=-p[i].a/p[i].b;p[i].id=i;
}
sort(&p[1],&p[n+1]);
CDQ(1,n);
printf("%.3lf\n",f[n]);
return 0;
}
方法二:平衡树
因为每个点的\(x\)坐标并不是严格递增的
因此我们需要支持动态插点,动态维护凸壳
想清楚怎么插吧。。。
假设我们已经维护了一个上图壳
如果新插的点在凸壳内部,这个点可以直接扔掉
如果在凸壳外部,那么相当于要删掉凸壳上的一段点
找到左侧第一个能够和它一起卡在凸壳上的点
找到右侧第一个能够和它一起卡在凸壳上的点
把中间的点全部删掉就行了
然后我就写死了。。。
这个貌似可以用\(set\)做吧。。。
左右暴力找就行了(因为只要找到了就可以直接删掉)
求答案的时候二分一下就行了。。。
我的STL水平显然不够啊。。。
稍微注意一个细节:这题口口声声说不卡精度,但是如果不卡\(eps\)会挂。。。。
#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<set>
#include<map>
#include<vector>
#include<queue>
using namespace std;
#define ll long long
#define RG register
#define MAX 111111
#define INF 1e9
int n;
double a[MAX],b[MAX],r[MAX];
double x[MAX],y[MAX],f[MAX];
double K(int i,int j)
{
if(fabs(x[i]-x[j])<1e-7)return -1e20;
return (y[i]-y[j])/(x[i]-x[j]);
}
struct SplayTree
{
int root,tot;
struct Node
{
int ch[2],ff;
double k1,k2;
}t[MAX];
void rotate(int x)
{
int y=t[x].ff,z=t[y].ff;
int k=t[y].ch[1]==x;
if(z)t[z].ch[t[z].ch[1]==y]=x;t[x].ff=z;
t[y].ch[k]=t[x].ch[k^1];t[t[x].ch[k^1]].ff=y;
t[x].ch[k^1]=y;t[y].ff=x;
}
void Splay(int x,int goal)
{
while(t[x].ff!=goal)
{
int y=t[x].ff,z=t[y].ff;
if(z!=goal)(rand()&1)?rotate(x):rotate(y);
rotate(x);
}
if(!goal)root=x;
}
int findpre(int x)
{
int u=t[x].ch[0],ret=u;
while(u)
if(K(u,x)<=t[u].k1)ret=u,u=t[u].ch[1];
else u=t[u].ch[0];
Splay(ret,x);
return ret;
}
int findnxt(int x)
{
int u=t[x].ch[1],ret=u;
while(u)
if(K(u,x)>=t[u].k2)ret=u,u=t[u].ch[0];
else u=t[u].ch[1];
Splay(ret,x);
return ret;
}
void insert()
{
int u=root,nw=++tot;
if(!root){root=nw;return;}
while(233)
if(x[nw]-x[u]<=1e-7)
{
if(!t[u].ch[0]){t[u].ch[0]=nw;t[nw].ff=u;break;}
else u=t[u].ch[0];
}
else
{
if(!t[u].ch[1]){t[u].ch[1]=nw;t[nw].ff=u;break;}
else u=t[u].ch[1];
}
}
void Work(int nw)
{
Splay(nw,0);
if(t[nw].ch[0])
{
int lt=findpre(nw);t[t[lt].ch[1]].ff=0;t[lt].ch[1]=0;
t[lt].k2=t[nw].k1=K(lt,nw);
}
else t[nw].k1=INF;
if(t[nw].ch[1])
{
int rg=findnxt(nw);t[t[rg].ch[0]].ff=0;t[rg].ch[0]=0;
t[rg].k1=t[nw].k2=K(nw,rg);
}
else t[nw].k2=-INF;
if(t[nw].k1<=t[nw].k2)
{
t[root=t[nw].ch[0]].ff=0,t[t[root].ch[1]=t[nw].ch[1]].ff=root;
t[root].k2=t[t[nw].ch[1]].k1=K(t[nw].ch[1],root);
}
}
int Query(double K)
{
int u=root;
while(233)
{
if(!u)return 0;
if(t[u].k2<=K&&t[u].k1>=K)return u;
if(t[u].k1<K)u=t[u].ch[0];
else u=t[u].ch[1];
}
}
}Splay;
int main()
{
scanf("%d%lf",&n,&f[0]);
for(int i=1;i<=n;++i)scanf("%lf%lf%lf",&a[i],&b[i],&r[i]);
for(int i=1;i<=n;++i)
{
int j=Splay.Query(-a[i]/b[i]);
f[i]=max(f[i-1],x[j]*a[i]+y[j]*b[i]);
y[i]=f[i]/(a[i]*r[i]+b[i]);
x[i]=y[i]*r[i];
Splay.insert();Splay.Work(i);
}
printf("%.3lf\n",f[n]);
return 0;
}
【BZOJ1492】【NOI2007】货币兑换(动态规划,CDQ分治,Splay)的更多相关文章
- [BZOJ1492] [NOI2007] 货币兑换Cash(cdq分治+斜率优化)
[BZOJ1492] [NOI2007] 货币兑换Cash(cdq分治+斜率优化) 题面 分析 dp方程推导 显然,必然存在一种最优的买卖方案满足:每次买进操作使用完所有的人民币:每次卖出操作卖出所有 ...
- bzoj1492[NOI2007]货币兑换Cash cdq分治+斜率优化dp
1492: [NOI2007]货币兑换Cash Time Limit: 5 Sec Memory Limit: 64 MBSubmit: 5541 Solved: 2228[Submit][Sta ...
- BZOJ 1492 [NOI2007]货币兑换Cash (CDQ分治/splay 维护凸包)
题目大意:太长了略 splay调了两天一直WA弃疗了 首先,我们可以猜一个贪心,如果买/卖,就一定都买/卖掉,否则不买/卖 反正货币的行情都是已知的,没有任何风险,所以肯定要选择最最最优的方案了 容易 ...
- BZOJ1492: [NOI2007]货币兑换Cash(CDQ分治,斜率优化动态规划)
Description 小Y最近在一家金券交易所工作.该金券交易所只发行交易两种金券:A纪念券(以下简称A券)和 B纪念券(以下 简称B券).每个持有金券的顾客都有一个自己的帐户.金券的数目可以是一个 ...
- BZOJ1492:[NOI2007]货币兑换 (CDQ分治+斜率优化DP | splay动态维护凸包)
BZOJ1492:[NOI2007]货币兑换 题目传送门 [问题描述] 小Y最近在一家金券交易所工作.该金券交易所只发行交易两种金券:A纪念券(以下简称A券)和B纪念券(以下简称B券).每个持有金券的 ...
- BZOJ 1492: [NOI2007]货币兑换Cash [CDQ分治 斜率优化DP]
传送门 题意:不想写... 扔链接就跑 好吧我回来了 首先发现每次兑换一定是全部兑换,因为你兑换说明有利可图,是为了后面的某一天两种卷的汇率差别明显而兑换 那么一定拿全利啊,一定比多天的组合好 $f[ ...
- [NOI2007]货币兑换 「CDQ分治实现斜率优化」
首先每次买卖一定是在某天 $k$ 以当时的最大收入买入,再到第 $i$ 天卖出,那么易得方程: $$f_i = \max \{\frac{A_iRate_kf_k}{A_kRate_k + B_k} ...
- LOJ 2353 & 洛谷 P4027 [NOI2007]货币兑换(CDQ 分治维护斜率优化)
题目传送门 纪念一下第一道(?)自己 yy 出来的 NOI 题. 考虑 dp,\(dp[i]\) 表示到第 \(i\) 天最多有多少钱. 那么有 \(dp[i]=\max\{\max\limits_{ ...
- [BZOJ1492][NOI2007]货币兑换Cash(斜率优化+CDQ分治)
1492: [NOI2007]货币兑换Cash Time Limit: 5 Sec Memory Limit: 64 MBSubmit: 5838 Solved: 2345[Submit][Sta ...
- BZOJ1492: [NOI2007]货币兑换Cash 【dp + CDQ分治】
1492: [NOI2007]货币兑换Cash Time Limit: 5 Sec Memory Limit: 64 MB Submit: 5391 Solved: 2181 [Submit][S ...
随机推荐
- vmware因为软件出过一次复制的错误导致不能复制到主机的解决方法
只需要把vmware的虚拟机进程全部结束掉,然后重置(先设置不勾选复制等,然后保存后在勾选上并保存)一次虚拟机隔离设置(需要在关闭虚拟机的情况下设置,否则就是灰色不允许操作),然后再开启虚拟机,就能正 ...
- 高能福利 |"荐"者有份,有"福"同享
WeTest 导读 越来越多的开发者加入WeTest大家庭了,感谢大家一直以来的支持,WeTest又有新一步“大福利”赠送了! 即日起,参加WeTest用户推荐有礼活动,推荐者和被推荐者皆可获得福利. ...
- 学习HTML 第一节.小试牛刀
此贴并非教学,主要是自学笔记,所述内容只是些许个人学习心得的记录和备查积累,难以保证观点正确,也不一定能坚持完成. 如不幸到访,可能耽误您的时间,也难及时回复,贴主先此致歉.如偶有所得,相逢有缘,幸甚 ...
- rocketmq Lock failed,MQ already started -c参数
今天部署rocketmq集群时一台机器部署一个master 和slave,slave部署总是失败,通过查看日志显示下面的错误 java.lang.RuntimeException: Lock fail ...
- pytest使用笔记(三)——pytest+allure+jenkins配置使用
按照pytest使用笔记(二)把pytest+allure配置好后,现在在jenkins配置好,先实现手动构建(立个小目标) 一,安装jenkins插件 首页->系统管理->插件管理,从“ ...
- 虹软2.0版本离线人脸识别C#类库分享
目前只封装了人脸检测部分的类库,供大家交流学习,肯定有问题,希望大家在阅读使用的时候及时反馈,谢谢!使用虹软技术开发完成 戳这里下载SDKgithub:https://github.com/dayAn ...
- Halcon如何保存仿射变换矩阵
这里我们通过序列化来实现的,如下图,写到硬盘的HomMat2D_1内容和从硬盘里HomMat2D_2读出的内容一致,源代码在图片下方. Halcon源代码: hom_mat2d_identity (H ...
- leetcode个人题解——#43 Multiply Strings
思路:高精度乘法就可以了. 有两个错误以前没在意,1.成员属性定义时候不能进行初始化, vector<); 这样隐性调用了函数进行初始化的形式特别要注意,也是错误的: 2.容器类只有分配了空间时 ...
- springmvc 静态资源 配置
SpringMVC提供<mvc:resources>来设置静态资源,但是增加该设置如果采用通配符的方式增加拦截器的话仍然会被拦截器拦截,可采用如下方案进行解决: 方案一.拦截器中增加针对静 ...
- 0421--"数字口袋精灵app"二次开发(Blackbriar团队开发)
"数字口袋精灵app"二次开发 目录: 一.项目github总仓库推送 二.开发成员 三.分工与合作 四.各模块成果 五.心得墙 六.团队成员贡献分 内容: 一.项目github总 ...