【洛谷5308】[COCI2019] Quiz(WQS二分+斜率优化DP)
大致题意: 你要把\(n\)划分恰好\(k\)个非空段,设第\(i\)段长为\(a_i\),求\(\sum_{i=1}^k\frac{a_i}{\sum_{j=1}^ia_j}\)的最大值。
\(WQS\)二分
考虑到恰好\(k\)段这个限制,显然划分为更多的段数必然能够得到更优的答案,因此可以\(WQS\)二分。
具体地,二分选择一段的额外代价\(C\),只要在每次选取新一段时给答案减去\(C\)即可。
斜率优化\(DP\)
考虑我们设\(f_i\)表示划分出\(1\sim i\)所能得到的最大收益,暴力想法就是枚举一个\(j\),得到:
\[f_i=f_j+\frac {i-j}i-C=f_j-\frac ji+1-C
\]
其中\(1-C\)明显是常数项,因此对于两个转移点\(x,y\)(\(x>y\)),\(x\)的答案优于\(y\)的答案需要满足:
\[f_x-\frac xi>f_y-\frac yi
\]
\[\frac{x-y}i<f_x-f_y
\]
\[i>\frac {x-y}{f_x-f_y}
\]
因此我们只要开一个单调队列,维护斜率单调递增,每次弹出不再有贡献的队首,再从队首转移即可。
代码
#include<bits/stdc++.h>
#define Tp template<typename Ty>
#define Ts template<typename Ty,typename... Ar>
#define Reg register
#define RI Reg int
#define Con const
#define CI Con int&
#define I inline
#define W while
#define N 100000
#define DB long double
#define eps 1e-12
using namespace std;
int n,k,q[N+5],g[N+5];DB f[N+5];//f记录最大收益,g记录划分段数
I int Check(Con DB& C)//斜率优化DP,C为当前额外代价
{
RI i,H=1,T=0;for(q[++T]=0,i=1;i<=n;++i)//初始放入0
{
W(H<T&&i*(f[q[H+1]]-f[q[H]])>q[H+1]-q[H]) ++H;f[i]=f[q[H]]-1.0*q[H]/i+1-C,g[i]=g[q[H]]+1;//弹出不优队首,然后从队首转移
W(H<T&&(q[T]-q[T-1])*(f[i]-f[q[T]])>(i-q[T])*(f[q[T]]-f[q[T-1]])) --T;q[++T]=i;//在队尾加入当前点,维护斜率递增
}return g[n];//返回在当前额外代价下的最大收益
}
int main()
{
scanf("%d%d",&n,&k);DB l=0,r=1e9,mid;
W(r-l>eps) Check(mid=(l+r)/2)<=k?r=mid:l=mid;//WQS二分
return Check(r),printf("%.9Lf",f[n]+k*r),0;//最终答案
}
待到再迷茫时回头望,所有脚印会发出光芒