题目
有
n
n
n个物品,价值为
A
i
A_i
Ai。取得一个物品需要花费
t
i
t_i
ti的时间。
设
T
=
∑
t
i
T=\sum t_i
T=∑ti。这些物品都要取完,总共要花
T
T
T的时间。
物品的价值会随着时间递增而减少。
如果在时刻
x
x
x取完物品
i
i
i,则此时收获的价值为
A
i
(
1
−
c
x
T
)
A_i(1-\frac{cx}{T})
Ai(1−Tcx)
求
c
c
c的最大值,使得:对于所有的最终获得价值尽量多的最优方案,都不存在满足
A
i
>
A
j
A_i>A_j
Ai>Aj的
i
,
j
i,j
i,j同时满足
A
i
(
1
−
c
x
i
T
)
<
A
j
(
1
−
c
x
j
T
)
A_i(1-\frac{cx_i}{T})<A_j(1-\frac{cx_j}{T})
Ai(1−Tcxi)<Aj(1−Tcxj)。
n
≤
2
e
5
n\leq2e5
n≤2e5
思考历程&正解
看到部分分中的
A
i
t
i
\frac{A_i}{t_i}
tiAi,心中有些想法。知道自己不会证于是写了程序拍一下发现是对的……
(具体证明可以考虑相邻物品的顺序)
接下来的问题是若何决定
A
i
t
i
\frac{A_i}{t_i}
tiAi相同的块中内部的顺序。
于是接下来另外猜结论,证明靠拍,搞了半天发现下一个结论是错了……
后来发现我思想僵化,以为一定存在一种排列方式,使得它的
c
c
c永远是最大的……
在乱搞的时候很容易发现这条式子:
T
c
≥
A
i
x
i
−
A
j
x
j
A
i
−
A
j
\frac{T}{c}\geq\frac{A_ix_i-A_jx_j}{A_i-A_j}
cT≥Ai−AjAixi−Ajxj
右边的那条东西显然是个斜率的形式。
那么可以考虑,对于
i
i
i来说,最大的斜率是多少。
那么我们希望
x
i
x_i
xi尽量大,
x
j
x_j
xj尽量小。
于是钦定
i
i
i所在块最后一个被选的(时间记为
R
i
R_i
Ri),
j
j
j为所在块第一个被选的(时间记为
L
i
L_i
Li)。这个时间可以预处理出来。
按照
A
i
A_i
Ai从小往大的顺序操作。建立平面直角坐标系。在加入
A
i
A_i
Ai之前,计算当前坐标系中的点和
(
A
i
,
A
i
R
i
)
(A_i,A_iR_i)
(Ai,AiRi)的最大斜率;计算之后将
(
A
i
,
A
i
L
i
)
(A_i,A_iL_i)
(Ai,AiLi)丢入坐标系中。
比赛时我二分
1
c
\frac{1}{c}
c1的值,将其转化为判定性问题之后,发现特别好写……
如果我不是被垃圾C++坑了,我就AC了……(二分的精度设置了1e-10
,然后到了这个精度之后
m
i
d
mid
mid算出来永远跟
l
l
l一样,然后就死循环了……)
比赛之后gmh77发表了他的
O
(
n
)
O(n)
O(n)的撵爆正解的方法:
不需要二分,只需要比较相邻点即可……
具体的理解,可以自己画图看看,或者根据二分之后判定的代码,不难发现这个是等价的。
代码
using namespace std;
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <climits>
#define N 200010
#define ll long long
int n;
struct Problem{
int a,t;
} p[N];
bool cmpp(Problem x,Problem y){return (ll)x.a*y.t>(ll)y.a*x.t;}
double L[N],R[N];
int q[N];
bool cmpq(int x,int y){return p[x].a<p[y].a;}
bool judge(double k){
double b=1e17,lstx=0;
for (int i=1;i<=n;++i){
double x=p[q[i]].a,y=p[q[i]].a*R[q[i]];
b+=(x-lstx)*k;
if (lstx<x && b<y)
return 0;
y=p[q[i]].a*L[q[i]];
b=min(b,y);
lstx=x;
}
return 1;
}
int main(){
// freopen("in.txt","r",stdin);
// freopen("out.txt","w",stdout);
freopen("score.in","r",stdin);
freopen("score.out","w",stdout);
scanf("%d",&n);
for (int i=1;i<=n;++i)
scanf("%d",&p[i].a);
ll T=0;
for (int i=1;i<=n;++i)
scanf("%d",&p[i].t),T+=p[i].t;
sort(p+1,p+n+1,cmpp);
p[0]={1,0},p[n+1]={0,1};
ll sumt=0;
for (int i=1;i<=n;++i){
L[i]=((ll)p[i].a*p[i-1].t==(ll)p[i-1].a*p[i].t?L[i-1]:sumt);
sumt+=p[i].t;
}
for (int i=1;i<=n;++i)
L[i]+=p[i].t;
for (int i=n;i>=1;--i){
R[i]=((ll)p[i].a*p[i+1].t==(ll)p[i+1].a*p[i].t?R[i+1]:sumt);
sumt-=p[i].t;
}
for (int i=1;i<=n;++i)
L[i]/=T,R[i]/=T;
for (int i=1;i<=n;++i)
q[i]=i;
sort(q+1,q+n+1,cmpq);
double l=0,r=1e16;
while (r-l>1e-8){
double mid=(l+r)/2;
if (judge(mid))
r=mid;
else
l=mid;
}
double c=1/r;
c=min(c,1.0);
printf("%.9lf\n",c);
return 0;
}
总结
二分精度不能太大,学到了……