纪念一下第二道 ( ext{wqs}) 二分题(我好弱啊),感觉题目很不错。
题解
按照 ( ext{wqs}) 二分的套路来,我们先不考虑轮数限制,大体思路上是需要倒序思考 (dp) 过程,即
[f_i=max_{j<i}(f_j+frac{i-j}{i})
]
但是你发现这个 ( ext{dp}) 是 (O(n^2)) 的,本身就需要优化,发现可以斜率优化,即
[f_{i}=f_j+frac{i-j}{i}\
f_icdot i=f_jcdot i+i-j\
(f_j+1)cdot i-f_icdot i=j\
]
我们维护点 ((f_j+1,j)) 的下凸包,并在转移的时候用斜率 (i) 去切它就可以了。
我们在此时需要将 ( ext{wqs}) 二分结合进去,即在每一次转移中都需要减去我们当前二分到的斜率 (k) ,可以证明,这个斜率 (kin (0,1]) ,即
[f_i=max_{j<i}(f_j+frac{i-j}{i}-k)\
(f_j+1-k)cdot i-f_{i}cdot i=j
]
然后每一次二分后的 ( ext{dp}) 就维护点 ((f_j+1-k,j)) 的下凸包,并用斜率 (i) 去切它就可以了。
代码
#include<bits/stdc++.h>
using namespace std;
#define double long double
const int N=1e5+5;
int n,k;
struct Point{double x,y;};
struct Vector{double x,y;};
Vector operator - (Point a,Point b){return (Vector){a.x-b.x,a.y-b.y};}
double operator * (Vector a,Vector b){return a.x*b.y-a.y*b.x;}
double res=0,f[N];int g[N];
pair<Point,int> bag[N];
pair<int,double> cal(double k){
int L=1,R=0;
memset(f,0,sizeof(f)),memset(g,0,sizeof(g));
f[1]=g[1]=1,bag[++R]=make_pair((Point){f[1]+1-k,1},g[1]);
for(int i=2;i<=n;++i){
while(R-L>0&&(bag[L+1].first-bag[L].first)*(Vector){1,1.0*i}>0) L++;
f[i]=bag[L].first.x-bag[L].first.y/i,g[i]=bag[L].second+1;
Point tmp=(Point){f[i]+1-k,1.0*i};
while(R-L>0&&(bag[R].first-bag[R-1].first)*(tmp-bag[R-1].first)<0) R--;
bag[++R]=make_pair(tmp,g[i]);
}
// for(int i=1;i<=n;++i) printf("%.9lf %d
",f[i],g[i]);
return make_pair(g[n],f[n]);
}
int main(){
cin>>n>>k;
double L=0,R=1;
while((R-L)>1e-16){
double Mid=(L+R)/2;pair<int,double> tmp=cal(Mid);
// printf("%.9lf %d %.9lf
",Mid,tmp.first,tmp.second);
if(tmp.first>=k) res=tmp.second+Mid*(k-1),L=Mid;
else R=Mid;
}
return printf("%.9Lf
",res),0;
}