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 }
题目链接:http://www.lydsy.com/JudgeOnline/problem.php?id=1492
题目大意:小Y最近在一家金券交易所工作。该金券交易所只发行交易两种金券:A纪念券(以下简称A券)和 B纪念券(以下
只有一个实数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 }
我们设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]货币兑换Cash(斜率优化+CDQ分治)
BZOJ 1492: [NOI2007]货币兑换Cash( dp + 平衡树 )