• 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
    ",cash[i].k);
    74     sort(cash+1,cash+n+1,comp);
    75 //    for (int i=1;i<=n;i++) printf("%d %lf
    ",cash[i].id,cash[i].k);
    76     solve(1,n);
    77     printf("%.3lf
    ",ans);
    78 //    for (int i=1;i<=n;i++) printf("%d
    ",g[i]);
    79 //    for (int i=1;i<=n;i++) printf("%.3lf
    ",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
    ",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分治+斜率优化。

  • 相关阅读:
    JUC学习
    java反射学习
    JSON入门学习
    redis
    NoSQ学习
    手写Lockl锁
    MapReduce过程
    scala学习
    idea jetty 配置
    java 基础--理论知识
  • 原文地址:https://www.cnblogs.com/OYzx/p/5527887.html
Copyright © 2020-2023  润新知