bzoj1492: [NOI2007]货币兑换Cash

Posted oyzx~

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了bzoj1492: [NOI2007]货币兑换Cash相关的知识,希望对你有一定的参考价值。

 1 #include <iostream>
 2 #include <cstdio>
 3 #include <cstring>
 4 #include <algorithm>
 5 #include <cmath>
 6 #define maxn 100005
 7 #define Inf 1000000000
 8 using namespace std;
 9 
10 const double inf=1e-30;
11 int n,g[maxn],head,tail,list[maxn];
12 double ans,st,f[maxn],a[maxn],b[maxn],ri[maxn];
13 struct date{
14     double a,b,r,k;
15     int id;
16 }cash[maxn],temp[maxn],p;
17 struct note{
18     double x,y;
19     int id;
20 }point[maxn],tmp[maxn],pp;
21 
22 bool comp(date x,date y){
23     return x.k>y.k;
24 }
25 
26 double getk(int x,int y){
27     if (!y) return -Inf;
28     if (point[x].x==point[y].x) return Inf;
29     return (point[x].y-point[y].y)/(point[x].x-point[y].x);
30 }
31 
32 void solve(int l,int r){
33     if (l==r){
34         ans=max(ans,f[g[l]]*(a[l]+b[l]/ri[g[l]]));
35         f[l]=(ri[l]*ans)/(a[l]*ri[l]+b[l]);
36         point[l].x=f[l],point[l].y=f[l]/ri[l],point[l].id=l;
37         return;
38     }
39     int mid=(l+r)/2;
40     for (int i=l,j=mid+1,k=l;k<=r;k++){
41         if (cash[k].id<=mid) temp[i++]=cash[k];
42         else temp[j++]=cash[k];
43     }
44     for (int i=l;i<=r;i++) cash[i]=temp[i];
45     solve(l,mid);
46     head=1,tail=0;
47     for (int i=l;i<=mid;i++){
48         while (head<tail&&getk(list[tail],i)>getk(list[tail],list[tail-1])+inf) tail--;
49         list[++tail]=i;
50     }
51     for (int i=mid+1;i<=r;i++){
52         p=cash[i];
53         while (head<tail&&getk(list[head],list[head+1])>p.k+inf) head++;
54         pp=point[list[head]];
55         if ((p.a*pp.x+p.b*pp.y>p.a*f[g[p.id]]+p.b*f[g[p.id]]/ri[g[p.id]]+inf)||!g[p.id]) g[p.id]=pp.id;
56     }
57     solve(mid+1,r);
58     for (int i=l,j=mid+1,k=l;i<=mid||j<=r;){
59         if (j>r||(i<=mid&&point[i].x<point[j].x)) tmp[k++]=point[i++];
60         else tmp[k++]=point[j++];
61     }
62     for (int i=l;i<=r;i++) point[i]=tmp[i];
63 }
64 
65 int main(){
66     memset(g,0,sizeof(g));
67     scanf("%d%lf",&n,&st),ans=st;
68     for (int i=1;i<=n;i++){
69         scanf("%lf%lf%lf",&cash[i].a,&cash[i].b,&cash[i].r),cash[i].id=i;
70         a[i]=cash[i].a,b[i]=cash[i].b,ri[i]=cash[i].r;
71         cash[i].k=-cash[i].a/cash[i].b;
72     }
73 //    for (int i=1;i<=n;i++) printf("%.3lf\\n",cash[i].k);
74     sort(cash+1,cash+n+1,comp);
75 //    for (int i=1;i<=n;i++) printf("%d %lf\\n",cash[i].id,cash[i].k);
76     solve(1,n);
77     printf("%.3lf\\n",ans);
78 //    for (int i=1;i<=n;i++) printf("%d\\n",g[i]);
79 //    for (int i=1;i<=n;i++) printf("%.3lf\\n",f[i]);
80     return 0;
81 }
View Code

题目链接:http://www.lydsy.com/JudgeOnline/problem.php?id=1492

题目大意:小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天后最多能
够获得多少元钱。
样例输入:
输入第一行两个正整数N、S,分别表示小Y能预知的天数以及初始时拥有的钱数。接下来N行,第K行三个实数AK、B
K、RateK,意义如题目中所述。对于100%的测试数据,满足:0<AK≤10;0<BK≤10;0<RateK≤100;MaxProfit≤1
0^9,n<=1*10^5。
【提示】
1.输入文件可能很大,请采用快速的读入方式。
2.必然存在一种最优的买卖方案满足:
每次买进操作使用完所有的人民币;
每次卖出操作卖出所有的金券。
样例输出:

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

 

做法:初看这题,仔细思考这题的性质,我们发现最优的方案一定是每次买进操作使用完所有的人民币,每次卖出操作卖出所有的金券,于是n^2dp是很容易想到的,我们设f[i]表示第i天最多剩下的A金券的数目,ans表示当前最多的人民币,状态转移方程为:

 1 #include <iostream>
 2 #include <cstdio>
 3 #include <cstring>
 4 #include <algorithm>
 5 #include <cmath>
 6 #define maxn 100005 
 7 using namespace std;
 8 
 9 int n;
10 double ans,s,f[maxn],a[maxn],b[maxn],r[maxn]; 
11 
12 int main(){
13     double x;
14     scanf("%d%lf",&n,&s),ans=s;
15     for (int i=1;i<=n;i++) scanf("%lf%lf%lf",&a[i],&b[i],&r[i]);
16     f[1]=(r[1]*ans)/(a[1]*r[1]+b[1]);
17     for (int i=2;i<=n;i++){
18         for (int j=1;j<=i-1;j++){
19             ans=max(ans,a[i]*f[j]+b[i]*f[j]/r[j]);
20         }
21         f[i]=(r[i]*ans)/(r[i]*a[i]+b[i]);
22     }
23     printf("%.3lf\\n",ans);
24     return 0;
25 }
View Code

我们设j,k为i的两个决策,j比k优当且仅当:ai*fj+bi*fj/rj>ai*fk+bi*fk/rk,化简可得:(fj/rj-fk/rk)/(fj-fk)<-ai/bi,设gi=fi/ri,设fj<fk,所以(gj-gk)/(fj-fk)<-ai/bi,我们可以维护一个凸线,-ai/bi是可以预处理得到的,fi与gi是在cdq分治的过程中可以求出来的,通过这个我们我可以求出每个i的最优决策。

这题其实有两种做法,一种是平衡树维护凸线,这一种比较恶心,以后再来补坑吧,第二种是cdq分治,我用的是cdq分治做法,我们定义solve(l,r)过程,执行了该过程后,就能得到f[l~r],我们的目标便是solve(1,n)。

cdq分治过程如下:

可以先预处理一个数组-ai/bi,保证单调下降。

solve(l,r){

      先保证先后顺序,O(n)扫一遍即可,具体可见代码。

      solve(l,mid);l~mid这一段的f值已求出,已经按照f值从小到大排好序了。

      对l~mid这一段建立凸线,mid+1~r在凸线上扫一遍更新最优决策即可(由于是单调的,对于第二个区间,我们只需要找到第一条直线的斜率小与其-a/b即可,这个过程用两个指针扫一遍即可)。

     solve(mid+1,r);

     两个区间都已经f都已经求出并单调递增,归并排序保证整个l~rf值单调递增,为后代造福。

}

这就是cdq分治的整个过程,代码实现比较恶心,详见代码。

恶心的cdq分治+斜率优化题,写了四五天了。

cdq分治+斜率优化。

以上是关于bzoj1492: [NOI2007]货币兑换Cash的主要内容,如果未能解决你的问题,请参考以下文章

BZOJ1492:[NOI2007]货币兑换——题解

BZOJ1492: [NOI2007]货币兑换Cash

[BZOJ1492][NOI2007]货币兑换Cash(斜率优化+CDQ分治)

BZOJ 1492: [NOI2007]货币兑换Cash( dp + 平衡树 )

bzoj1492[NOI2007]货币兑换Cash cdq分治+斜率优化dp

[BZOJ1492] [NOI2007]货币兑换Cash 斜率优化+cdq/平衡树维护凸包