●BZOJ 1492 [NOI2007]货币兑换Cash
题链:
http://www.lydsy.com/JudgeOnline/problem.php?id=1492
题解:
斜率优化DP,CDQ分治
定义$DP[i]$为第i天结束后的最大收益。
由于题目给了良心的提示,转移就比较明显了:
令 $X_j,Y_j$ 分别表示用第j天的最大收益去全部买票,得到的A,B票的数量,
那么转移如下:
$DP[i]=min(X_jA_i+Y_jB_i)\quad(j<i)$ 第j天买的票在第i天全部卖出
$DP[i]=min(DP[i],DP[i-1])$ 不做任何交易,直接继承上一天的答案。
显然这个转移是 $O(N^2)$ 的。
(另外,$X_j=\frac{DP[j]R_j}{A_jR_j+B_j},Y_j=\frac{DP[j]}{A_jR_j+B_j}$)
然后考虑优化,比如从j位置转移到i位置:
$DP[i]=X_jA_i+Y_jB_i$,可以化为以下形式:
$\mathbf{Y_j=-\frac{A_i}{B_i}X_j+\frac{DP[i]}{B_i}}$
现在在来看看这个式子,由于$A_i,B_i$都是确定的正数,可以考虑为常量,
如果把转移来源点$(X_j,Y_j)$都看成第一象限的点,
那么此时问题变为:
已知的斜率 $k=-\frac{A_i}{B_i}$,找到平面中的一个点,使得过该点的斜率为$k$的直线的纵截距最大。
怎样维护比较快捷呢?
做法是对这些点维护一个上凸壳,那么对于任何斜率$k$,使得纵截距最大的点一定在这个凸壳的顶点上。
即凸壳内部的点永远不可能贡献答案。
证明如下:

以上是第一种情况,下面再来看看内部的点与顶点的x不相同的情况。
先来看一个结论:

那么现在在来看看凸壳:

所以以上证明了使得纵截距最大的点一定在凸壳上。
到目前为止,我们的做法就是:
对于当前计算的DP[i],把i的来源点j看成平面上的点,然后对这些点维护好一个上凸壳。
然后在凸壳上找到一个点使得过该点,且斜率为$k=-\frac{A_i}{B_i}$时的直线的纵截距最大。
关键点:
1.要注意DP转移的顺序,即只能从前面转移到后面。
2.维护好转移来源点的上凸壳。
由于Xi不随着i单增,所以不能一边从左枚举到右,一边O(1)插入一个新点并维护好凸壳。
同时询问不单调,所以不能直接像某些斜率优化的题一样用一个单调队列维护。
方法有两种:
1.CDQ分治:$O(Nlog_2N)$
分治的每一层,对于l~mid的点维护好一个凸壳,
然后mid+1~r点按$-\frac{A_i}{B_i}$从大到小排好序,然后扫一遍凸壳给mid+1~r的点贡献答案。
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#define MAXN 100500
using namespace std;
const double eps=1e-10;
double A[MAXN],B[MAXN],R[MAXN];
double X[MAXN],Y[MAXN],DP[MAXN];
int h[MAXN],N;
int sign(double x){
if(-eps<=x&&x<=eps) return 0;
return x>eps?1:-1;
}
bool cmp(int i,int j){
return sign(-A[i]/B[i]-(-A[j]/B[j]))>0;
}
bool cmp2(int i,int j){
return sign(X[i]-X[j])<0;
}
struct Moque{//斜率单调递减的上凸壳
int q[MAXN],l,r;
void Reset(){l=1;r=0;}
#define Slope(i,j) ((Y[i]-Y[j])/(X[i]-X[j]))
void Push(int i){
if(l<=r&&sign(X[i]-X[q[r]])==0)
{if(sign(Y[i]-Y[q[r]])>0) r--;else return;}
while(l+1<=r&&sign(Slope(i,q[r])-Slope(q[r],q[r-1]))>=0) r--;
q[++r]=i;
}
int Query(int i){
while(l+1<=r&&sign(Slope(q[l],q[l+1])-(-A[i]/B[i]))>0) l++;
return q[l];
}
}Q;
void solve(int l,int r){
static double MAXDP=0;
static int tmp[MAXN],cl,cr,p;
if(l==r){
Y[h[l]]=DP[h[l]]/(A[h[l]]*R[h[l]]+B[h[l]]);
X[h[l]]=Y[h[l]]*R[h[l]];
return;
}
int mid=(l+r)>>1; cl=l; cr=mid+1;
for(int i=l;i<=r;i++)
if(h[i]<=mid) tmp[cl++]=h[i];
else tmp[cr++]=h[i];
for(int i=l;i<=r;i++) h[i]=tmp[i];
solve(l,mid); Q.Reset();
for(int i=l;i<=mid;i++) Q.Push(h[i]),MAXDP=max(MAXDP,DP[h[i]]);
for(int i=mid+1,j;i<=r;i++){
j=Q.Query(h[i]);
DP[h[i]]=max(DP[h[i]],MAXDP);
DP[h[i]]=max(DP[h[i]],X[j]*A[h[i]]+Y[j]*B[h[i]]);
}
solve(mid+1,r); cl=l; cr=mid+1; p=l;
while(cl<=mid||cr<=r){
if(cl>mid) tmp[p]=h[cr],cr++;
else if(cr>r||sign(X[h[cl]]-X[h[cr]])<0) tmp[p]=h[cl],cl++;
else tmp[p]=h[cr],cr++; p++;
}
for(int i=l;i<=r;i++) h[i]=tmp[i];
}
int main(){
scanf("%d%lf",&N,&DP[1]);
for(int i=1;i<=N;i++)
h[i]=i,scanf("%lf%lf%lf",&A[i],&B[i],&R[i]);
// printf("happy\n");
sort(h+2,h+N+1,cmp);
solve(1,N);
// for(int i=1;i<=N;i++)
printf("%.3lf\n",DP[N]);
return 0;
}
2.Splay:$O(Nlog_2N)$
动态维护好凸壳并直接log级别询问的最优转移点即可,Splay要支持单点插入,区间删除。
(感觉凸壳和Splay搭在一起好奇妙(恶心),本来以为有1mol需要特判的东西,结果由于凸壳的特殊,都不用特判了。。。)
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#define MAXN 100500
using namespace std;
const double eps=1e-10;
double A[MAXN],B[MAXN],R[MAXN];
double X[MAXN],Y[MAXN],DP[MAXN];
int N;
int sign(double x){
if(-eps<=x&&x<=eps) return 0;
return x>eps?1:-1;
}
struct SPT{
#define Slope(i,j) ((Y[i]-Y[j])/(X[i]-X[j]))
int ch[MAXN][2],fa[MAXN],pre[MAXN],suf[MAXN],d[MAXN],sz,rt,t1,t2;
void Rotate(int x,int &k){
static int y,z,ll,rr;
y=fa[x]; z=fa[y];
ll=ch[y][0]!=x; rr=ll^1;
if(!z) k=x; else ch[z][ch[z][0]!=y]=x;
fa[ch[x][rr]]=y; fa[y]=x; fa[x]=z;
ch[y][ll]=ch[x][rr]; ch[x][rr]=y;
}
void Splay(int x,int &k){
static int y,z;
while(x!=k){
y=fa[x]; z=fa[y];
if(y!=k) ((ch[y][0]!=x)^(ch[z][0]!=y))?
Rotate(x,k):Rotate(y,k);
Rotate(x,k);
}
}
int Insert(int &x,int dad,int i,int l,int r){//l,r维护新插入点的前驱后继
if(!x){
x=++sz; fa[x]=dad; d[x]=i;
suf[l]=x; pre[x]=l; suf[x]=r; pre[r]=x;
Splay(x,rt); return sz;
}
if(sign(X[i]-X[d[x]])==0){//发现横坐标相同的点
if(sign(Y[i]-Y[d[x]])>0){//留下纵坐标大的
d[x]=i; Splay(x,rt); return x;
}
else return 0;//舍去纵坐标小的
}
if(sign(X[i]-X[d[x]])<0) return Insert(ch[x][0],x,i,l,x);
else return Insert(ch[x][1],x,i,x,r);
}
void Findpre(int x,int i){
if(!x) return;
if(pre[x]){
if(sign(Slope(i,d[x])-Slope(d[x],d[pre[x]]))>0)
t1=x,Findpre(ch[x][0],i);
else Findpre(ch[x][1],i);
}
}
void Findsuf(int x,int i){
if(!x) return;
if(suf[x]){
if(sign(Slope(d[suf[x]],d[x])-Slope(d[x],i))>0)
t2=x,Findsuf(ch[x][1],i);
else Findsuf(ch[x][0],i);
}
}
int Split(int ll,int rr){
Splay(ll,rt);
Splay(rr,ch[rt][1]);
return ch[rr][0];
}
void Delete(int ll,int rr){//删除开区间(ll,rr)
static int p;
p=Split(ll,rr);
if(!p) return;
ch[rr][0]=0; fa[p]=0;
suf[ll]=rr; pre[rr]=ll;//维护好前驱后继
}
void Push(int i){
static int p;
p=Insert(rt,0,i,0,0);//插入后,被旋转到根
if(!p) return;
if(pre[p]&&suf[p]&&sign(Slope(d[suf[p]],d[p])-Slope(d[p],d[pre[p]]))>0){//判断是否在凸壳内,是的话就删除并退出
Delete(pre[p],suf[p]);
return;
}
t1=t2=0;//以下维护凸壳
Findpre(ch[rt][0],i);
Findsuf(ch[rt][1],i);
if(t1) t1=pre[t1],Delete(t1,p);
if(t2) t2=suf[t2],Delete(p,t2);
}
int Query(double w){
static int x; x=rt;
while(x){
if(pre[x]&&sign(Slope(d[x],d[pre[x]])-w)<0) x=ch[x][0];
else if(suf[x]&&sign(Slope(d[suf[x]],d[x])-w)>0) x=ch[x][1];
else break;
}
return d[x];
}
}DT;
int main(){
freopen("cash.in","r",stdin);
freopen("cash.out","w",stdout);
scanf("%d%lf",&N,&DP[1]);
for(int i=1;i<=N;i++)
scanf("%lf%lf%lf",&A[i],&B[i],&R[i]);
Y[1]=DP[1]/(A[1]*R[1]+B[1]); X[1]=Y[1]*R[1];
DT.Push(1);
for(int i=2,j;i<=N;i++){
j=DT.Query(-A[i]/B[i]);
DP[i]=max(DP[i-1],X[j]*A[i]+Y[j]*B[i]);
Y[i]=DP[i]/(A[i]*R[i]+B[i]); X[i]=Y[i]*R[i];
DT.Push(i);
}
//for(int i=1;i<=N;i++)
printf("%.3lf\n",DP[N]);
return 0;
}
●BZOJ 1492 [NOI2007]货币兑换Cash的更多相关文章
- BZOJ 1492: [NOI2007]货币兑换Cash( dp + 平衡树 )
dp(i) = max(dp(i-1), x[j]*a[i]+y[j]*b[i]), 0<j<i. x, y表示某天拥有的最多钱去买金券, 金券a和金券b的数量. 然后就很明显了...平衡 ...
- bzoj 1492 [NOI2007]货币兑换Cash(斜率dp+cdq分治)
[题目链接] http://www.lydsy.com/JudgeOnline/problem.php?id=1492 [题意] 有AB两种货币,每天可以可以付IPi元,买到A券和B券,且A:B= ...
- 斜率优化(CDQ分治,Splay平衡树):BZOJ 1492: [NOI2007]货币兑换Cash
Description Input 第一行两个正整数N.S,分别表示小Y 能预知的天数以及初始时拥有的钱数. 接下来N 行,第K 行三个实数AK.BK.RateK,意义如题目中所述 Output 只有 ...
- BZOJ 1492: [NOI2007]货币兑换Cash [CDQ分治 斜率优化DP]
传送门 题意:不想写... 扔链接就跑 好吧我回来了 首先发现每次兑换一定是全部兑换,因为你兑换说明有利可图,是为了后面的某一天两种卷的汇率差别明显而兑换 那么一定拿全利啊,一定比多天的组合好 $f[ ...
- bzoj 1492: [NOI2007]货币兑换Cash
Description 小Y最近在一家金券交易所工作.该金券交易所只发行交易两种金券:A纪念券(以下简称A券)和 B纪念券(以下 简称B券).每个持有金券的顾客都有一个自己的帐户.金券的数目可以是一个 ...
- BZOJ 1492 [NOI2007]货币兑换Cash:斜率优化dp + cdq分治
传送门 题意 初始时你有 $ s $ 元,接下来有 $ n $ 天. 在第 $ i $ 天,A券的价值为 $ A[i] $ ,B券的价值为 $ B[i] $ . 在第 $ i $ 天,你可以进行两种操 ...
- bzoj 1492: [NOI2007]货币兑换Cash【贪心+斜率优化dp+cdq】
参考:http://www.cnblogs.com/lidaxin/p/5240220.html 虽然splay会方便很多,但是懒得写,于是写了cdq 首先要想到贪心的思路,因为如果在某天买入是能得到 ...
- BZOJ 1492 [NOI2007]货币兑换Cash (CDQ分治/splay 维护凸包)
题目大意:太长了略 splay调了两天一直WA弃疗了 首先,我们可以猜一个贪心,如果买/卖,就一定都买/卖掉,否则不买/卖 反正货币的行情都是已知的,没有任何风险,所以肯定要选择最最最优的方案了 容易 ...
- BZOJ 1492: [NOI2007]货币兑换Cash 斜率优化 + splay动态维护凸包
Description 小Y最近在一家金券交易所工作.该金券交易所只发行交易两种金券:A纪念券(以下简称A券)和 B纪念券(以下 简称B券).每个持有金券的顾客都有一个自己的帐户.金券的数目可以是一个 ...
随机推荐
- python实现朴素贝叶斯
参考:<机器学习实战>- Machine Learning in Action 一. 基本思想 简单的说,用概率的高低来决定数据属于哪一类别,这就是贝叶斯决策理论的核心思想,即选择具有最 ...
- C语言--第三周作业
一.PTA作业中4个题目 1.7-9 A乘以B 要求:输入的两个整数:A是你学号前两位数字,B是你学号后两位数字 a.代码 #include <stdio.h> int main () { ...
- SaaS的那些事儿
前两年... 大一大二期间,不知道软件架构.云服务器.数据库为何物,偶尔听过却从未用过.天天学的写的东西都是一些命令行代码,所幸在学完<数据结构>和<算法导论>后能够独立实 ...
- Python randrange() 函数
Python randrange() 函数 Python 数字 描述 randrange() 方法返回指定递增基数集合中的一个随机数,基数缺省值为1. 语法 以下是 randrange() 方法的语 ...
- AWS中,如果使用了ELB,出现outofservice
平台:亚马逊AWS EC2 出现状况: 我创建了弹性平衡负载,也注册了实例,但是实例的状态一直是outofservice.为什么? 为什么会出现这个问题呢? 1:实例有问题: 2:负载平衡器创建的有问 ...
- 亚马逊的PuTTY连接AWS出现network error connection refused,终极解决方案。
使用PuTTY连接AWS的时候,一直出现network error connection refused.百度了这个问题,大家都说是SSH要设置成22.但是我已经设置过了,为什么还是遇到这个问题呢? ...
- 如何用tomcat实现类似weblogic那样的热部署方式
平时weblogic部署程序包时一般是到控制台去部署,不需要重启. 相反之前用tomcat部署应用时,我一般都是把tomcat重启来完成程序包的更新或新包部署.但是这次要部署的应用有点多,大概10几个 ...
- 原始的Ajax方法 (异步的 JavaScript 和 XML -- (Extensible Markup Language 可扩展标记语言))
<script language="javascript" type="text/javascript"> var request = false; ...
- Mego开发文档 - 基本保存操作
基本保存操作 在Mego中没有更改跟踪,也就是说所有的新增.更新及删除都需要开发者自行判断.Mego会最为实际的将各个数据操作提交给数据库并执行. 添加数据 using (var db = new O ...
- C# 解析json数据出现---锘縖
解析json数据的时候出现 - 锘縖,不知道是不是乱码,反正我是不认识这俩字.后来发现是json的 '[' 字符转换的 网上搜了一下,说的是字符集不匹配,把字符集改为GB2312. 一.贴下处理jso ...