P5308 [COCI2019] Quiz

纪念一下第二道 ( 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;
}
原文地址:https://www.cnblogs.com/Point-King/p/14564703.html