BZOJ1492 NOI2007 货币兑换 题解与代码

 

其实解决这个问题的突破口在提示:

设f[i]表示第i天全部将A,B券换成人民币的最多数目,p[i].a表示A券在第i天最多拥有的个数,p[i].b表示B券在第i天最多拥有的个数,则:

那么,对于第i天最后一次购买,它在第j天(i<j)的折合人民币就是:

于是:

因此,我们得到一个动态规划的算法,框架如下:

p[1].b=S / (A[1] * Rate[1] + B [1]) 
Ans=S
For  i = 2  to  n ← 枚举在哪一天卖出券
       For  j = 1  to  i-1   ← 枚举在哪一天买入券
               x=p[ j ].a * A[i ] + p[j ].b * B [i ]
              Ans=max{Ans,x}
       End For
       p [i ].b = Ans / (A[i ] * Rate[i ] + B[i ])
End For
Print(Ans)

我们可以把最后一次购入金劵的时间看作一种决策。现在考虑两种不同的决策在此时哪种能够获得最大的折合人民币。假设第i天A劵的价值为A[i],B劵的价值为B[i],两个不同的决策是:最后一次在第x天和第y天买入金劵。那么,决策x不如决策y优等价于:

这样我们就可以用平衡树以p[j ].a为关键字来维护一个凸线,平衡树维护一个点集(p[j ].a, p[j ].b),p[j ].a是单调递增的,相邻两个点的斜率是单调递减的.每次在平衡树中二分查找与-A[i ] / B[i ]最接近的两点之间的斜率

这样其实就转化成了斜率优化的动态规划

可是用平衡树维护,编程复杂度高了

 

事实上,我们可以利用分治的思想来提出一个编程复杂度比较低的方法:

首先我们按斜率-A[i]/B[i]从小到大排序,对于每一个i,它的决策j 的范围为1~i-1;我们定义一个Solve过程:Solve(l, r )

设mid=(l+r)/2,类似归并排序,我们用[l,r]的前半部[l,mid],来更新[l,r]区间的后半部[mid+1,r]的答案值,即在第i天我能得到的最大人民币数值,其中i∈[mid+1,r]

对于当前处理的区间[l,r],我们可以先Solve(l, mid ),这样保证在区间[l,mid]内的所有答案值均已被更新

然后对该区间的前半部建立一个凸壳(凸线),对于参与建立凸壳的点,需保证其水平序有序;用这个凸壳(凸线)去更新这个区间的后半部,其中后半部的所有元素,其-A[i]/B[i],即斜率,均有序,而凸壳的斜率也是单调的,这样正好可以用来解决本区间后半部的答案更新问题

而前半部分的排序只需在每次某区间全部处理完毕后,将该区间的所有元素<即将前半部分和后半部分合并>按水平序排序即可,后半部的元素在整个solve过程之前已整体按斜率排过一遍序

注意到,对于一个区间的前半部和后半部的作用是不同的,前半部的元素的答案值均已更新过答案值,为后半部的更新提供凸壳,后半部的元素的答案值更新由前半部构造的凸壳来更新答案,两部分的顺序互不影响。这样,我们只需要保证前半部的元素按水平序排序,后半部分按斜率排序,这样就相当于用一系列连续的直线去切一些连续点组成的凸壳。

给出一个算法框架:

Procedure Solve(l, r)
If l = r Then
    直接更新答案值
Exit
Solve(l, mid )
→对[l, mid ]这一段扫描一遍计算出凸线
→扫描一遍更新[mid +1 , r]的最优决策
→ Solve(mid+1, r)
→将两部分合并,按水平序排序 
 End Procedure

时间复杂度分析:递归主过程:构造凸壳以及更新答案: O(n),那么递归方程为:T(n)=2T(n/2)+O(n),即时间复杂度为O(nlogn)

 

从这个例题中,我们可以看出,其solve过程的实质为:

       →Solve(L,middle)

       →处理[L,middle]中元素对[middle+1,R]中f[x]取值的影响

       → Solve(middle+1,R)

而在该问题中,处理影响部分相当于是更新答案值部分;使用了维护凸壳,斜率DP等方法来在尽可能优的时间内更新答案

 

附代码:

#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<algorithm>
#include<iostream>
#include<cmath>
using namespace std;
#define MAXN 100001
#define EPS 1e-7
#define IMAX 21474836.0
struct DAY{double a,b,rate,k;int num;}a[MAXN],use[MAXN];
struct MONEY{double a,b;}p[MAXN],tmp[MAXN];
int N,stack[MAXN];
double S,f[MAXN];
bool cmp1(DAY X,DAY Y){return X.k<Y.k;}
double getk(int num1,int num2)
{
      if(!num1)   return -IMAX;
      if(!num2)   return IMAX;
      if(fabs(p[num1].a-p[num2].a)<=EPS)
            return IMAX;
      return (p[num1].b-p[num2].b)/(p[num1].a-p[num2].a);
}
void mergesort(int left,int right)
{
      int middle=(left+right)/2;
      int L1=left,L2=middle+1;
      for(int i=left;i<=right;i++)
      {
            if(((p[L1].a<p[L2].a || (fabs(p[L1].a-p[L2].a)<=EPS && p[L1].b<p[L2].b)) || L2>right) && L1<=middle)
                  tmp[i]=p[L1++];
            else  tmp[i]=p[L2++];
      }
      for(int i=left;i<=right;i++)
            p[i]=tmp[i];      
}
void solve(int left,int right)
{
      if(left==right)
      {
            if(left==1)   f[left]=f[left]<S?S:f[left];
            else f[left]=f[left]<f[left-1]?f[left-1]:f[left];
            p[left].b=f[left]/(a[left].a*a[left].rate+a[left].b);
            p[left].a=p[left].b*a[left].rate;
            return;
      }
      int middle=(left+right)/2;
      int num1=left,num2=middle+1;
      
      for(int i=left;i<=right;i++)
      {
            if(a[i].num<=middle)
                  use[num1++]=a[i];
            else  use[num2++]=a[i];
      }
      for(int i=left;i<=right;i++)
            a[i]=use[i];
      
      solve(left,middle);
      	
      int top=0,now=1;
      for(int i=left;i<=middle;i++)
      {
            while(top-1>=1 && getk(i,stack[top])>getk(stack[top],stack[top-1]))
                  top--;
            stack[++top]=i;
      }
      for(int i=right;i>=middle+1;i--)
      {
            while(now+1<=top && (a[i].k<getk(stack[now],stack[now+1])))
                  now++;
            f[a[i].num]=max(f[a[i].num],a[i].a*p[stack[now]].a+a[i].b*p[stack[now]].b);
      }
      
      solve(middle+1,right);
      
      mergesort(left,right);
}
int main()
{
      //freopen("cash.in","r",stdin);
      //freopen("cash.out","w",stdout);
      scanf("%d%lf",&N,&S);
      for(int i=1;i<=N;i++)
      {
            scanf("%lf%lf%lf",&a[i].a,&a[i].b,&a[i].rate);
            a[i].k=-1*a[i].a/a[i].b;
            a[i].num=i;
      }
      sort(a+1,a+1+N,cmp1);
      solve(1,N);
      printf("%.3lf\n",f[N]);
      return 0;
}

转载注明出处:https://blog.csdn.net/csyzcyj/

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
以下是一份使用Treap实现的C++代码,仅供参考。 ```c++ #include <bits/stdc++.h> using namespace std; const int INF = 0x3f3f3f3f; const int MAXN = 1e5 + 5; int n, m; int root, cnt; struct Node { int val, key; int size; int ch[2]; } tr[MAXN]; inline int new_node(int val) { tr[++cnt].val = val; tr[cnt].key = rand(); tr[cnt].size = 1; return cnt; } inline void pushup(int p) { tr[p].size = 1 + tr[tr[p].ch[0]].size + tr[tr[p].ch[1]].size; } inline void split(int p, int k, int &x, int &y) { if (!p) { x = y = 0; return; } if (tr[p].val <= k) { x = p; split(tr[p].ch[1], k, tr[p].ch[1], y); } else { y = p; split(tr[p].ch[0], k, x, tr[p].ch[0]); } pushup(p); } inline int merge(int x, int y) { if (!x || !y) return x + y; if (tr[x].key < tr[y].key) { tr[x].ch[1] = merge(tr[x].ch[1], y); pushup(x); return x; } else { tr[y].ch[0] = merge(x, tr[y].ch[0]); pushup(y); return y; } } inline void insert(int val) { int x, y; split(root, val, x, y); root = merge(merge(x, new_node(val)), y); } inline void remove(int val) { int x, y, z; split(root, val, x, z); split(x, val - 1, x, y); y = merge(tr[y].ch[0], tr[y].ch[1]); root = merge(merge(x, y), z); } inline int kth(int k) { int p = root; while (p) { if (tr[tr[p].ch[0]].size >= k) { p = tr[p].ch[0]; } else if (tr[tr[p].ch[0]].size + 1 == k) { return tr[p].val; } else { k -= tr[tr[p].ch[0]].size + 1; p = tr[p].ch[1]; } } return -1; } inline int query_min() { int p = root; int res = INF; while (p) { res = min(res, tr[p].val); p = tr[p].ch[0]; } return res; } int main() { srand(time(NULL)); scanf("%d%d", &n, &m); for (int i = 1, opt, x; i <= n; ++i) { scanf("%d%d", &opt, &x); if (opt == 1) { insert(x); } else { remove(tr[cnt].val); } } printf("%d\n", query_min()); while (m--) { int k; scanf("%d", &k); printf("%d\n", kth(k)); } return 0; } ``` 代码实现中使用了一个结构体 `Node` 来表示平衡树的每一个节点。其中 `val` 表示节点存储的值,`key` 表示随机生成的优先级,`size` 表示以该节点为根的子树大小,`ch[0]` 和 `ch[1]` 分别表示左右儿子的下标。接下来就是一些经典的平衡树操作,例如 `split` 和 `merge` 用于分裂和合并平衡树,`insert` 和 `remove` 用于插入和删除节点,`kth` 和 `query_min` 用于查询平衡树中第 $k$ 小的数和最小值。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值