一. 问题引入 | 暴力dp
为了叙述方便:
#define A 宝贝球
#define B 超级球
同时为了避免歧义,我们令 (qwq) 为题目中的 (b)
设 (f_{i,j,k}) 表示前 (i) 个 Pokemon ,用了 (j) 个 A 以及 (k) 个 B 的最大期望。
转移方程显然:
答案为 (f_{n,q,qwq}) 复杂度 (O(n^3))
二. WQS二分 | 优化
在平面上选出形如 ((m,f_{n,a,m})) 的若干个点,显然其为凸函数(如果多选一个球,期望是增加的,球用的越多,期望增的越慢),但是我们不能直接求出该凸函数的样子(直接求就是上面的暴力),而我们需要知道的为该函数在 (m=qwq) 时的取值,于是我们考虑通过其他方式确定 (y_m)。
由于其凸函数的性质,那么任意引一条直线,当该函数过图像上一点且在 (y) 轴上的截距取到最大值时,此直线与该函数图像相切(如图)。
(图片来源于 Creeper_LKF 大佬的博客)
设该直线斜率为 (k),那么可设该直线方程为: (f_{n,a,m}=km+b),则有:(b=f_{n,a,m}-km),如果我们能对于一个 (k) 快速求出 (b) 的最大值((m) 为自变量)以及取到最大值时 (m) 的值,那么我们就能求出该直线与该函数的切点横坐标 (x)((x=m))。如果 (x<qwq),由于凸函数的性质,我们可以适当减小该直线的斜率使其切点右移,同理,如果 (x>qwq),我们可以适当增大该直线的斜率使其切点左移,这个调大调小的过程可以使用二分,保证了算法的复杂度正确。当二分完毕的时候,我们便得到了(x=qwq) 的时候的 (k) 以及 (b),便可求出 (f_{n,a,qwq}),也就是我们要求的答案。
于是问题转化为求 (f_{n,a,m}-km) 的最大值,其中 (m) 可任取。
考虑设 (g_{i,j}=maxlimits_{m} { f_{i,j,m}-km }),那么关于 (g) 的转移方程可以写成:
而 (m) 就是 (g_{n,a}) 从第三、四中情况转移过来的次数(可以理解为每用一个 B ,都要携带一个权值 (k)),可以在 dp 过程中很好维护。(代码中的 cnt[i][j]
表示 (g_{i,j}) 从第三、四中情况转移过来的次数)。
上述过程可以在 (O(n^2)) 的时间内完成。
我们来梳理一下算法流程:
- 在外层二分 (k)
- 对于一个确定的 (k),以 (O(n^2)) 的复杂度求出 (g_{n,a}) ,也即 (b=f_{n,a,m}-km) 的最大值,同时在这个过程中求出使 (b) 取到最大值的 (m)。
- 如果 (x<qwq),往小二分
- 同理,如果 (x>qwq),往大二分
- 最终当 (x=qwq),求得答案
总复杂度 (O(n^2 log n))
代码
const int N=2050;
int n,a,b,cnt[N][N];
double p[N],q[N],g[N][N];
inline bool check(double k)
{
memset(g,0,sizeof(g)),memset(cnt,0,sizeof(cnt));
fr(i,1,n) fr(j,0,a)
{
g[i][j]=g[i-1][j],cnt[i][j]=cnt[i-1][j];
if(j!=0&&g[i-1][j-1]+p[i]>=g[i][j]) g[i][j]=g[i-1][j-1]+p[i],cnt[i][j]=cnt[i-1][j-1];
if(g[i-1][j]+q[i]-k>=g[i][j]) g[i][j]=g[i-1][j]+q[i]-k,cnt[i][j]=cnt[i-1][j]+1;
if(j!=0&&g[i-1][j-1]+p[i]+q[i]-p[i]*q[i]-k>=g[i][j]) g[i][j]=g[i-1][j-1]+p[i]+q[i]-p[i]*q[i]-k,cnt[i][j]=cnt[i-1][j-1]+1;
}
return cnt[n][a]<=b;
}
int main(void)
{
n=read(),a=read(),b=read();
fr(i,1,n) scanf("%lf",&p[i]);
fr(i,1,n) scanf("%lf",&q[i]);
double l=0,r=1;int lim=60;
while(lim--)
{
double mid=(l+r)/2;
if(check(mid)) r=mid;
else l=mid;
}
double k=l;//最后二分出来的斜率
printf("%.5lf
",g[n][a]+k*b);//因为最后二分出来的 m 为 b
return 0;
}
三. 一点总结
带权二分适用于如下问题:
求出限制取某种东西的个数恰好为 (x)(可设 (f_x) 为恰好用 (x) 个时的最值),在这个限制下的最值,同时要求由若干个形如 ((x,f_x)) 的点构成的函数具有凸性。
四. 关于正确性的一点证明
事实上,我们发现这个函数的凸性似乎一点用都没有。因为即使这个函数不是凸函数(甚至没有单调性也可以),也照样能满足“直线斜率减小使切点右移,直线斜率增大使切点左移”,那么,问题到底出在哪里呢?
观察下图,事实上,当直线斜率变小的时候,切点依次为 (B,D,F) ,我们发现不在凸壳上的点其实是没有被考虑到的,所以答案的点有可能根本二分不到。(这也是许多网上的感性理解没有谈到的问题,“附加权值”变大或变小,可能导致最优决策点从来没有考虑到我们限制的那个位置)
为了满足所有点都在凸壳上,我们便要求它为一个凸函数。
根据此,我们可以对 WQS 分治作一点没用的推广:我们只需保证答案所在的点在所有点形成的凸壳上即可,单调性与凸性其实只是这个的充分条件。
(这只是个人思考得出的结论,不保证正确性 /kk)