ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

凸包与动态规划——浅谈斜率优化与 wqs 带权二分

2021-01-01 13:32:39  阅读:324  来源: 互联网

标签:二分 浅谈 wqs long 凸包 斜率 切点 单调


广告

你还在为题目限制太多而放弃吗?

你还在为dp太慢而焦虑吗?

你还在为炸空间而烦恼吗?

来试试凸包斜率优化与 wqs 带权二分吧!

简介

(上面怎么又有广告)

前置芝士你需要知道的:动态规划在转移时,最终转移而来的状态称为决策点

斜率优化与 wqs 带权二分都是利用凸包与切线的概念优化 dp 的工具

但是两者算法几乎完全不同,注意不要搞混

斜率优化是利用决策点的特性通过单调栈直接优化

wqs带权二分是通过二分斜率将限制转化为普通的加权问题

那,我们从凸包开始说起吧

凸包与切线

凸包

凸包,众所周知,就是凸起的小山包

为了偷懒方便,本文所提到的凸包都是开口向下的(即斜率单调递减)

  • 在数学上的定义好像是:

若存在, \(f''(x)\) 恒大于或恒小于 \(0\)

也就是 \(f'(x)\) (斜率)有单调性


然而,在 OI 中凸包一般都不是光滑的

其实定义也是类似的:

经过相邻两点的直线斜率有单调性

  • 性质:从凸包中选出一些点连线,仍然是个凸包

证明很显然,斜率有单调性嘛

一次函数

\(y=kx+b\)

  • \(k\) :斜率

  • \(b\) :截距

图像就是根直线

不是这有啥好说的(

切线

凸包 + 一次函数 = 切线!(雾

数学上的定义:与凸包交且只交于一点

数学上一般求过某点的切线,求导即可

可是不光滑的呢?

……好像没办法求

其实我们可以换个方法:求指定斜率为 \(k\) 的切线与切点

过凸包上的每一点作斜率为 \(k\) 的直线试试?

哦?切点的截距是最大的

(其实原因就是,先把直线放到最上面,一点一点向下移,第一个碰到凸包的就是切点)

那类似的,在不光滑的凸包上也可以这么定义!

  • \(Q\):如果有两个点对应的截距一样怎么办?

  • \(A\):其实就是这两点间的连线的斜率和 \(k\) 相同,其实选哪一个都可以,但实际解题时需要确定好取最左边或最右边的

斜率优化

呃,凸包这种很数学的东西为什么会和动态规划结合在一起啊……

先别管凸包,来道动规题吧

题目

P3195 [HNOI2008]玩具装箱

给出序列 \(C_i\) ,可以分成若干组。若将 \(i\) 到 \(j\) 分为一组,则代价为 \((j-i+\sum\limits_{k=i}^j C_k-L)^2\) 。求代价最小值。

动态规划

显然 dp

秉承尽量低维的思想,设 \(f_i\) 为 \(i\) 为某组的最后一个时,前 \(i\) 个代价的最小值

转移方程显然

\[f_i=\min\limits_{0 \leq j<i}\{ f_j+(i-(j+1)+ \sum\limits_{k=j+1}^i C_j -L)^2\} \]

记 \(s_k=\sum\limits_{i=1}^k C_i\)

\[f_i=\min\limits_{0 \leq j<i}\{ f_j+(i-j-1+ s_i-s_j -L)^2\} \]

\(O(n^2)\)

一次函数与凸包

啥,这也能优化?

大佬:你看这个式子,只有 i(相当于常量) 和 j …… 决策点会不会有啥性质?

说干就干,把 j 作为主元试试

\[f_i= f_j+(-s_j-j+s_i+i+1-L)^2 \]

记 \(A_j=s_j+j\) ,\(B_i=s_i+i+1-L\)

\[f_i= f_j+(B_i-A_j)^2 \]

\[f_i= f_j+B_i^2+A_j^2-2B_iA_j \]

\[(f_j+A_j^2)= (-2B_i)A_j+(B_i^2-f_i) \]

\[y=kx+b \]

好家伙,这不就是个关于 \(j\) 的一次函数嘛?!

而且我们要求的 \(f_i\) 呢,随着 \(b\) 的增大而减小

所以能想到,其实就是在平面直角坐标系上有很多点 \((x,y)=(A_j,f_j+A_j^2)\)

决策点就是过每个点作斜率为 \(k\) 的直线,截距最大的点

想到了啥?

这不就是刚刚说的凸包的切线吗?

但是这又不是凸包……

但其实,如果有三个点形成了凹槽(即斜率不是单调递减的)

会发现中间那个点无论如何截距都比左边或右边的小

事实上,只有凸包上的点才有可能是决策点

那咋实现呢?

注意到 \(x=A_j=s_j+j\) 单调递增,所以可以一个一个加入

根据上面的理论,加入一个点时,它就能将前面与它形成凹槽的点踢掉

这样就只剩下凸包了

凸包的斜率单调递减,那拿单调栈维护就可以力

切线与切点

好吧,事情还没有结束

我们要算的是 切点 ,不是 凸包

直接去算显然不太现实

但是我们又发现 \(k=-2B_i\) 单调递减

也就是说,切点实际上是一点一点向右移

然后就很简单了,把单调栈改成单调队列(其实就是增加了从左边删除的操作),每次把尾指针移到切点就可以力

每个点入队/出队至多两次,于是就成功做到了 \(O(n)\) !

附:我们刚刚用到了什么条件?

  • dp 是一维的

  • \(k\) 具有单调性

  • \(x\) 单调递增(其实递减可能也行?)

  • \(b\) 与 \(f_i\) 相关

代码

贼短

#include<bits/stdc++.h>
using namespace std;
const long long N=5e4+5,INF=1e18;
long long n,L;
long long a[N],s[N],f[N]; 
long long q[N],h,t;//队列,尾指针,头指针

double cal(long long l,long long r){
	long long xl=l+s[l],xr=r+s[r];
	long long yl=f[l]+xl*xl,yr=f[r]+xr*xr;
	return 1.0*(yl-yr)/(xl-xr);
}//计算斜率

int main(){
	cin>>n>>L;
	for(long long i=1;i<=n;i++) cin>>a[i],s[i]=s[i-1]+a[i];
	memset(f,0x7f,sizeof(f));
	f[0]=0;
	h=1,t=1,q[1]=0;
	
	for(long long i=1;i<=n;i++){
		long long k=2*(i-1+s[i]-L);//斜率
		while(h<t && cal(q[h],q[h+1])<k) h++;//尾指针移到切点
		long long j=q[h];//决策点
		f[i]=f[j]+(i-j-1+s[i]-s[j]-L)*(i-j-1+s[i]-s[j]-L);//转移
		while(h<t && cal(q[t-1],q[t])>cal(q[t],i)) t--;//头指针维护凸包
		q[++t]=i;
	}
	cout<<f[n];
}

练习

二维的 dp ,还要求输出方案

不过本质还是一维 dp 套上斜率优化

(后面还会有其他练习的,所以这里只放一题)


wqs 带权二分

哇,好高大上的名字(其实我觉得叫“斜率二分”更好

虽然同是用凸包优化 dp ,但它与斜率优化截然不同(所以忘掉斜率优化吧

还记得二项式反演吗?

有时候问题中会出现恰好 \(k\) 个的限制,通过二项式反演(或者容斥)可以转化成钦定有 \(k\) 个的问题

wqs带权二分的作用其实和这个很像,只不过不是容斥:

有时候问题中会出现恰好 \(k\) 个的限制,通过 wqs 带权二分可以转化为 没有限制,但是每使用一次就多 \(w\) 的代价 的问题

不懂吗?没关系,往下看吧

题目

CF739E Gosha is hunting

你要抓神奇宝贝! 现在一共有 \(n\) 只神奇宝贝。 你有 \(a\) 个『宝贝球』和 \(b\) 个『超级球』,其抓到第 \(i\) 只神奇宝贝的概率分别是 \(p_i\) 和 \(q_i\) ,每种球不能在同一只神奇宝贝上使用多次。求最优策略下,抓到神奇宝贝的总个数期望最大值,保留五位小数。

\(n \leq 10^5\)

动态规划

设 \(f_{i,j,k}\) 为前 \(i\) 个神奇宝贝,用了 \(j\) 个宝贝球和 \(k\) 个神奇球的最大期望值

转移方程显然

\[f_{i,j,k}=\max\{f_{i-1,j,k},f_{i-1,j-1,k}+p_i,f_{i-1,j,k-1}+q_i,f_{i-1,j-1,k-1}+1-(1-p_i)(1-q_i)\} \]

\(O(n^3)\)

凸包

这个显然没办法斜率优化了,因为不存在决策点的说法

不过 \(f_{n,j,k}\) 在每行和每列上都是有凸性

什么,为啥?这里插入两个证明凸性的好方法

  • 凸性的证明方法:

    • 1.感性理解

    • 2.打表找规律(建议)

据极不完全统计,第二种方法的正确率可达 100% (雾)

好,不妨把 \(f_{n,a,k}\) 看成一个关于 \(k\) 的函数

那么函数图像就是一个上凸包

我们想求的是 \(f_{n,a,b}\),可是不能直接 \(O(n^3)\) 求……

有一个很巧妙的想法:

通过切线来算出 \(f_{n,a,b}\)

啥意思呢?

还是确定一个斜率 \(k\) ,来算切点

显然 \(k\) 越大,切点就越小

那我们二分 \(k\) ,每次算出切点,不就可以把过 \(b\) 的切线算出来了?


现在只需考虑确定 \(k\) 时怎么算出切点的横坐标与纵坐标

假设切线是 \(y=kx+b\)

刚刚也说过了,切点的 \(b\) 是最大的

\(b=y-kx\)

好,还记得 \(x\) 和 \(y\) 的意义吗?

\(x\) 是使用超级球的个数, \(y\) 是最大期望值

那不就相当于不限制使用超级球的个数,但是使用一个就有 \(k\) 的代价,求最大值(即 \(b\) )与取最大值时使用超级球的个数(即 \(x\) )吗?

当然,\(y=kx+b\) 也求得出来

\(O(n^2)\) dp 即可

还有一个二分呢,假设二分的总数量为 \(V\) (通常是斜率的范围除以精度,这里就是 \(\frac{1}{0.0001}=10000\))

(注意:实际做题时通常将精度开得小一点)

总时间复杂度为 \(O(n^2logV)\)

二分套二分

到这里其实你已经学会 wqs 带权二分力

但是这道题还过不去……

其实既然超级球能把限制去掉,为啥宝贝球不行呢?

一样的道理,在上面的基础上再套一层二分宝贝球的斜率

现在最终要处理的问题就是:

没有限制,但是使用宝贝球和超级球分别有 \(k1\) 和 \(k2\) 的代价,求最大值与取最大值时使用两种球的个数

啊这,这不直接贪心就行?

总时间复杂度为 \(O(nlog^2V)\)

细节

还有点麻烦的小问题

如果有多个点对应的截距一样怎么办?

其实就是这多点共线且连线的斜率和 \(k\) 相同,其实选哪一个都可以,但实际解题时需要确定好取最左边或最右边的

代码里写的是确定最左边的

也就是贪心时尽量不用

最后统计答案的时候乘上的就是二分的左指针

为啥要这样呢?

其实如果要求的点在多点共线中间的话是二分不到的,只能二分到最左边的点

但是就算没有二分到,斜率一定是多点共线的斜率,算出来的答案还是一样的

代码

#include<bits/stdc++.h>
using namespace std;
const int N=2200;
const double eps=1e-6;
int n,a,b;
double p[N],q[N];
double ua,ub,tot;

void check(double a,double b){
	ua=ub=tot=0;
	for(int i=1;i<=n;i++){
		double maxx=0;
		int cnta=0,cntb=0;
		if(maxx<p[i]-a-eps) maxx=p[i]-a,cnta=1,cntb=0;
		if(maxx<q[i]-b-eps) maxx=q[i]-b,cnta=0,cntb=1;
		if(maxx<1-(1-p[i])*(1-q[i])-a-b-eps)
			maxx=1-(1-p[i])*(1-q[i])-a-b,cnta=1,cntb=1;
		ua+=cnta,ub+=cntb;
		tot+=maxx;
	}
	
}


int main(){
	cin>>n>>a>>b;
	for(int i=1;i<=n;i++) cin>>p[i];
	for(int i=1;i<=n;i++) cin>>q[i];
	
	double la=0,ra=1,lb,rb;
   //外层二分
	while(la+eps<ra){
		double mida=(la+ra)/2;
		lb=0,rb=1;
      //内层二分
		while(lb+eps<rb){
			double midb=(lb+rb)/2;
			check(mida,midb);
			if(ub>=b) lb=midb;//说明切点偏右,斜率小了
			else rb=midb;
			if(ub==b) break;//如果切点二分到了b直接退出
		}
		if(ua>=a) la=mida;
		else ra=mida;
		if(ua==a) break;//如果切点二分到了a直接退出
	}
	cout<<tot+la*a+lb*b;
} 

练习

没有单独是 wqs 带权二分的练习……

所以来点套娃题!

wqs 带权二分套斜率优化

标签:二分,浅谈,wqs,long,凸包,斜率,切点,单调
来源: https://www.cnblogs.com/Appleblue17/p/14219912.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有