zoukankan      html  css  js  c++  java
  • 【题解】CF379E | 关于带权二分优化dp的一点理解

    一. 问题引入 | 暴力dp

    为了叙述方便:

    #define A 宝贝球
    #define B 超级球

    同时为了避免歧义,我们令 (qwq) 为题目中的 (b)

    (f_{i,j,k}) 表示前 (i) 个 Pokemon ,用了 (j) 个 A 以及 (k) 个 B 的最大期望。

    转移方程显然:

    [f_{i,j,k}=max {f_{i-1,j,k},f_{i-1,j-1,k}+a_i,f_{i-1,j,k-1}+b_i,f_{i-1,j-1,k-1}+1-(1-a_i)(1-b_i) } ]

    答案为 (f_{n,q,qwq}) 复杂度 (O(n^3))

    二. WQS二分 | 优化

    在平面上选出形如 ((m,f_{n,a,m})) 的若干个点,显然其为凸函数(如果多选一个球,期望是增加的,球用的越多,期望增的越慢),但是我们不能直接求出该凸函数的样子(直接求就是上面的暴力),而我们需要知道的为该函数在 (m=qwq) 时的取值,于是我们考虑通过其他方式确定 (y_m)

    由于其凸函数的性质,那么任意引一条直线,当该函数过图像上一点且在 (y) 轴上的截距取到最大值时,此直线与该函数图像相切(如图)。

    %%%Creeper_LKF
    (图片来源于 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) 的转移方程可以写成:

    [g_{i,j}=max {g_{i-1,j},g_{i-1,j-1}+a_i,g_{i-1,j}+b_i-k,g_{i-1,j-1}+1-(1-a_i)(1-b_i)-k } ]

    (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) ,我们发现不在凸壳上的点其实是没有被考虑到的,所以答案的点有可能根本二分不到。(这也是许多网上的感性理解没有谈到的问题,“附加权值”变大或变小,可能导致最优决策点从来没有考虑到我们限制的那个位置)

    为了满足所有点都在凸壳上,我们便要求它为一个凸函数。

    IVI3CT.png(自己画的图好丑哇)

    根据此,我们可以对 WQS 分治作一点没用的推广:我们只需保证答案所在的点在所有点形成的凸壳上即可,单调性与凸性其实只是这个的充分条件。
    (这只是个人思考得出的结论,不保证正确性 /kk)

  • 相关阅读:
    JAVA中“==”和equals
    C++中各种容器的类型与特点
    程序员面试宝典 笔记 第七章
    程序员面试宝典 笔记(第六章 预处理 const 和sizeof())
    某学长面经
    tomcat 启动日志乱码
    Jenkins关闭和重启实现方式
    linux下svn版本控制的常用命令大全
    Java中的增强 for 循环 foreach
    JS 中 cookie 的使用
  • 原文地址:https://www.cnblogs.com/lgj-lgj/p/15505722.html
Copyright © 2011-2022 走看看