【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 ...
随机推荐
- eclipse jetty debug
一. 1, Eeclipse中选择 Run --> External Tools --> External Tools Configurations 然后new一个Program项. ...
- Unity3D使用NGUI实现简单背包功能
前话 在许多类型游戏中我们经常会使用到背包,利用背包来设置相应角色属性,多了背包也会让游戏增色拓展不少. 那在Unity3D游戏开发中该如何编写背包系统呢?因为有高人开发了NGUI插件,因此我们进行简 ...
- Jenkins CLI 通过ssh方式链接时的证书
在Jenkins自己的配置文档下,并没有详细说明要如何生成ssh证书,不过随便网上查一查就会有很多. 这里记录一个坑: 这个ssh必须要用ssh2!!! 这个ssh必须要用ssh2!!! 这个ssh必 ...
- 利用工厂模式实现serviec层和dao层解耦
利用工厂模式实现serveice和dao层的解耦,这样就可以不用在service层实例化dao层的对象,当dao层代码发生改变的时候(数据库实现发生改变)直接修改配置文件就不用改变service层的代 ...
- 发送请求工具—Advanced REST Client的安装使用
1. 0 下载得到Advanced-REST-client_v3.1.9.zip 链接:http://pan.baidu.com/s/1c0vUnJi 密码:z34d 1.1 解压Advanced-R ...
- 转发——谷歌云官方:一小时掌握深度学习和 TensorFlow
转发——谷歌云官方:一小时掌握深度学习和 TensorFlow 本文转发自新智元,链接如下: http://mp.weixin.qq.com/s?__biz=MzI3MTA0MTk1MA==& ...
- TW实习日记:第五天
今天可以说是非常忙的一天了,要再项目中实现微信相关的功能:授权登录以及扫码登录,还有就是自建应用的发送消息.首先功能代码其实在经过了几天的学习之后并没有很难,但是最让我难受的是在项目中去加代码,首先s ...
- Java基础知识:Java实现Map集合二级联动4
comboBox.setModel(new DefaultComboBoxModel(getProvince())); // 添加省份信息 final JLabel label = new JLabe ...
- Python中如何实现im2col和col2im函数(sliding类型)
今天来说说im2col和col2im函数,这是MATLAB中两个内置函数,经常用于数字图像处理中.其中im2col函数在<MATLAB中的im2col函数>一文中已经进行了简单的介绍. 一 ...
- 高可用Kubernetes集群-8. 部署kube-scheduler
十.部署kube-scheduler kube-scheduler是Kube-Master相关的3个服务之一,是有状态的服务,会修改集群的状态信息. 如果多个master节点上的相关服务同时生效,则会 ...