好题+神题,首先肯定是dp,我们设f[i]为到第i天能获得的最多的B卷(设获得的钱数亦可)
由题目hint可知,要么全买要么全卖,我们有
f[i]=max(maxmoney,f[j]*b[i]+f[j]*rate[j]*a[i]))/(a[i]*rate[i]+b[i]),
这式子一看就是斜率优化,maxmoney可以先不管它
考虑决策j,k不妨设j<k,如果决策k优于j那么有
f[j]*b[i]+f[j]*rate[j]*a[i]<f[k]*b[i]+f[k]*rate[k]*a[i]
可以得到(f[k]*rate[k]-f[j]*rate[j])/(f[k]-f[j])>-b[i]/a[i]
我们设每个点是(f[k],f[k]*rate[k]),G(j,k)=(f[k]*rate[k]-f[j]*rate[j])/(f[k]-f[j])就是斜率
很明显我们要维护一个上凸壳,但这里我们不能用单调队列,因为后面不是单调的
可以用平衡树维护,但是我们观察这个式子
后面状态不会对前面状态产生影响(修改独立),并且不强制在线
于是我们可以用cdq分治来解决这个问题,我们还是根据天数顺序(状态)进行分治
分治过程中关键就是算[l,mid]部分整体对后面[mid+1.r]部分的影响
要能快速计算需要两个东西,首先我们要维护一个凸壳,
其次我们要按照单调的顺序处理后面部分的-b[]/a[],这样我们可以用单调队列来做,
我们可以先对-b[]/a[]降序排序(上凸线相邻两点间的斜率是递减的)
然后先递归处理处于左边(状态时[l,mid])的点,
在对一个区间都处理完毕之后,我们对这之间的点x为第一关键字排序,y为第二关键字排序
这样我们就能得到就得到了[l,mid]这部分状态排好序的点,然后我们维护单调队列,算出[l,mid]整体对[mid+1,r]的影响
然后我们再递归处理右边,完来之后再排序对[l,r]整体排序
在处理排序的时候,很很明显我们可以用归并排序,这样每次递归时处理得复杂度都是O(L) L是区间长度
所以根据主定理,T(n)=2T(n/2)+O(n) 复杂度是O(nlogn);
注意这道题凸线会挂精度,到底在什么时候要保留精度呢?请求指教
1 const eps=1e-9; 2 type node=record 3 a,b,ra,k,x,y:double; 4 po:longint; 5 end; 6 7 var a,b:array[0..100010] of node; 8 f:array[0..100010] of double; 9 q:array[0..100010] of longint; 10 i,n:longint; 11 12 function getk(x,y:longint):double; 13 begin 14 if abs(a[y].x-a[x].x)<eps then exit(1e21); 15 exit((a[y].y-a[x].y)/(a[y].x-a[x].x)); 16 end; 17 18 function cmp(a,b:node):boolean; 19 begin 20 exit((a.x<b.x) or (abs(a.x-b.x)<eps) and (a.y<b.y)); 21 end; 22 23 function max(a,b:double):double; 24 begin 25 if a>b then exit(a) else exit(b); 26 end; 27 28 procedure swap(var a,b:node); 29 var c:node; 30 begin 31 c:=a; 32 a:=b; 33 b:=c; 34 end; 35 36 procedure sort(l,r:longint); 37 var i,j:longint; 38 x:double; 39 begin 40 i:=l; 41 j:=r; 42 x:=a[(l+r) shr 1].k; 43 repeat 44 while x<a[i].k do inc(i); 45 while a[j].k<x do dec(j); 46 if not(i>j) then 47 begin 48 swap(a[i],a[j]); 49 inc(i); 50 dec(j); 51 end; 52 until i>j; 53 if l<j then sort(l,j); 54 if i<r then sort(i,r); 55 end; 56 57 procedure cdq(l,r:longint); 58 var m,i,l1,l2,t,j:longint; 59 begin 60 if l=r then 61 begin 62 f[l]:=max(f[l],f[l-1]); //这里f[]代表的是钱数 63 a[l].x:=f[l]/(a[l].ra*a[l].a+a[l].b); //获得B券的数目 64 a[l].y:=a[l].x*a[l].ra; 65 exit; 66 end; 67 m:=(l+r) shr 1; 68 l1:=l; l2:=m+1; 69 for i:=l to r do //先处理左部分的状态 70 if a[i].po<=m then 71 begin 72 b[l1]:=a[i]; 73 inc(l1); 74 end 75 else begin 76 b[l2]:=a[i]; 77 inc(l2); 78 end; 79 for i:=l to r do a[i]:=b[i]; 80 cdq(l,m); 81 82 t:=0; 83 for i:=l to m do 84 begin 85 while (t>1) and (getk(q[t-1],q[t])<getk(q[t-1],i)+eps) do dec(t); //维护凸壳 86 inc(t); q[t]:=i; 87 end; 88 j:=1; 89 for i:=m+1 to r do 90 begin 91 while (j<t) and (getk(q[j],q[j+1])+eps>a[i].k) do inc(j); //计算影响(就是找第一个小于的斜率的左端点) 92 f[a[i].po]:=max(f[a[i].po],a[q[j]].x*a[i].b+a[q[j]].y*a[i].a); 93 end; 94 95 cdq(m+1,r); 96 l1:=l; l2:=m+1; 97 for i:=l to r do //归并排序 98 if ((l2>r) or cmp(a[l1],a[l2])) and (l1<=m) then 99 begin 100 b[i]:=a[l1]; 101 inc(l1); 102 end 103 else begin 104 b[i]:=a[l2]; 105 inc(l2); 106 end; 107 108 for i:=l to r do a[i]:=b[i]; 109 end; 110 111 begin 112 readln(n,f[0]); 113 for i:=1 to n do 114 begin 115 readln(a[i].a,a[i].b,a[i].ra); 116 a[i].k:=-a[i].b/a[i].a; 117 a[i].po:=i; 118 end; 119 sort(1,n); 120 cdq(1,n); 121 writeln(f[n]:0:3); 122 end.