【DP】斜率优化初步
向y总学习了斜率优化,写下这篇blog加深一下理解。
模板题:https://www.acwing.com/problem/content/303/
分析
因为本篇的重点在于斜率优化,故在此给出状态转移方程:
\(f[i]=\min(f[j]-(t[i]+s)*c[j]+t[i]*c[i]+s*c[n])\) ,其中 \(j \in [1,i-1]\)
其中 \(f[i]\) 表示选取前 \(i\) 个物品后的最小贡献,\(t[i],c[i]\) 分别表示题目中前 \(i\) 个物品 \(t,c\) 的前缀和,\(n,s\) 与题面含义一致。
因为重点在于推式子,所以不理解里面的记号表示什么也不会有太大影响,不过还是尽量先看看朴素的DP怎么写。
如果采取朴素的递推方式解决,那么复杂度是 \(O(N^2)\) ,在 \(N\) 范围较大的时候会超时。
这时候,就要用到主角:斜率优化 了。
首先,因为要让 \(i\) 从 \(1-n\) 一直推过去,所以这里无法优化,因此考虑用更快的方式来找到 \(j\) 。
我们将上面的状态转移方程化为:
\(f[j]=(t[i]+s)*c[j]+f[i]-t[i]*c[i]-s*c[n]\)
注意到枚举 \(i\) 后, \(i\) 就是固定的了,在上面的方程中可以视为一个常数,而 \(j\) 则是变量。
我们将 \(f[j]\) 看作是因变量 \(y\) , \(c[j]\) 看作是自变量 \(x\) ,上面的式子可以简单地写成:
\]
其中 \(b=f[i]-t[i]*c[i]-s*c[n]\) ,\(k=(t[i]+s)\) 。
这个形式,正好是直线方程。
我们的目标是最小化 \(f[i]\),而注意到 \(-t[i]*c[i]-s*c[n]\) 是已经确定的了,所以最小化 \(b\) (直线对应的截距)即可。
因为直线是过点 \(P_{j}(x,y)\) 即 \((c[j],f[j])\) 的,所以下面考虑如何快速找到合适的点,让直线的截距最小化。
将 \(P_{j}(x,y) ~~j \in [1,i-1]\) 画在平面直角坐标系上:

现在要求斜率一定的直线在和什么样的点相交时截距取到最小。
接下来的内容可能需要一点线性规划基础。
1、首先,直线一定和下凸壳上的点相交时截距才可能取到最小:

(上图橙线所连的部分为下凸壳)
简证:假设直线 \(A\) 经过下凸壳上方的点,那么经过下凸壳的点的相同斜率直线 \(B\) 截距更小:

从这一事实中,我们想到维护一个凸包(下凸壳)。
下凸壳具有从左到右斜率递增的性质(所以考虑用单调队列来维护)
2、直线在和从左到右第一条斜率比它大的直线所对应的点(我们记为 \(P\) )相交时截距最小。
结合图像理解:

证明提示:考虑其他点(分左右讨论),发现没有比这个点更优的了。
下面的工作是实现:(不理解时可以结合代码)
可能需要单调队列基础
先开一个单调队列 q[] ,存点所对应的下标(注意!不是横坐标)
核心是维护一个凸包,注意到在这题中随着 \(i\) 的增加,直线的斜率是递增的,因而在从左到右寻找 \(P\) 的过程中,不是 \(P\) ( \(P\) 左边)的点出队就行了。(出队顺序从左到右)
在找到 \(P\) 后,对应的 \(j\) 就在队头,从而我们可以对 \(f[i]\) 进行更新。
下标 \(i\) 所对应的点是新点,需要进行入队,入队的时候要保证维护下凸壳,因此如果新点和先前的点形成的斜率是小于等于原来的点形成的斜率的,就要将原来的点出队。 (出队顺序从右到左)
如图:

最后将新点入队即可。
代码:
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=3e5+5;
int n,s;
ll c[N],t[N];
ll f[N];
int q[N];
int main(){
cin>>n>>s;
for(int i=1;i<=n;i++){
cin>>t[i]>>c[i];
c[i]+=c[i-1]; t[i]+=t[i-1];
}
int tt,hh;
tt=hh=0; //空的队列是tt=-1, hh=0 而队列一开始是有一个元素的
q[0]=0;
for(int i=1;i<=n;i++){
while(tt>hh && (f[q[hh+1]]-f[q[hh]])<=(t[i]+s)*(c[q[hh+1]]-c[q[hh]]) ) hh++; //弹出左边的点
int j=q[hh];
f[i]=f[j]-(t[i]+s)*c[j]+t[i]*c[i]+s*c[n]; //更新f
while(tt>hh && (__int128)(f[i]-f[q[tt-1]])*(c[q[tt]]-c[q[tt-1]])<=(__int128)(f[q[tt]]-f[q[tt-1]])*(c[i]-c[q[tt-1]])) tt--; //弹出右边的点
q[++tt]=i;
}
cout<<f[n]<<endl;
return 0;
}
复杂度
注意到每个点只可能入队,出队一次,故复杂度是 \(O(N)\)
扩展
上面例题的情况属于较为简单的情况,所对应的直线斜率随 \(i\) 增加而增加,而且更新点(入队的点)的横坐标也是递增的,因此在寻找 \(P\) 的过程中将 \(P\) 左边的点去掉即可。
但是,在性质不那么好的题目中,是可能出现斜率非单增的情况的,这时候我们就不能在寻找 \(P\) 的过程中将 \(P\) 左边的点去掉,凸包仍然是下凸壳(具有从左到右斜率递增的性质),故可以用二分法来寻找 \(P\) 。
这样做的复杂度是\(O(N\log N)\)
例题:https://www.acwing.com/problem/content/description/304/
代码
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=3e5+5;
ll t[N],c[N];
int n,s;
ll f[N];
int q[N];
int main(){
cin>>n>>s;
for(int i=1;i<=n;i++){
cin>>t[i]>>c[i];
t[i]+=t[i-1]; c[i]+=c[i-1];
}
int tt,hh;
tt=hh=0;
q[0]=0;
for(int i=1;i<=n;i++){
int l=hh,r=tt;
while(l<r){
int mid=l+r>>1;
if( (t[i]+s)*(c[q[mid+1]]-c[q[mid]])<=f[q[mid+1]]-f[q[mid]] ) r=mid;
else l=mid+1;
}
int j=q[l];
f[i]=f[j]-(t[i]+s)*c[j]+t[i]*c[i]+s*c[n];
while(tt>hh && (__int128)(f[i]-f[q[tt-1]])*(c[q[tt]]-c[q[tt-1]])<=(__int128)(f[q[tt]]-f[q[tt-1]])*(c[i]-c[q[tt-1]])) tt--;
q[++tt]=i;
}
cout<<f[n]<<endl;
return 0;
}
事实上,还有斜率不单调,更新点(入队的点)的横坐标也不单调的情况,这个时候就需要用平衡树等数据结构维护凸包了。
【DP】斜率优化初步的更多相关文章
- 【BZOJ】1096: [ZJOI2007]仓库建设(dp+斜率优化)
http://www.lydsy.com/JudgeOnline/problem.php?id=1096 首先得到dp方程(我竟然自己都每推出了QAQ)$$d[i]=min\{d[j]+cost(j+ ...
- 【BZOJ-4518】征途 DP + 斜率优化
4518: [Sdoi2016]征途 Time Limit: 10 Sec Memory Limit: 256 MBSubmit: 230 Solved: 156[Submit][Status][ ...
- 【BZOJ-3437】小P的牧场 DP + 斜率优化
3437: 小P的牧场 Time Limit: 10 Sec Memory Limit: 128 MBSubmit: 705 Solved: 404[Submit][Status][Discuss ...
- 【BZOJ-1010】玩具装箱toy DP + 斜率优化
1010: [HNOI2008]玩具装箱toy Time Limit: 1 Sec Memory Limit: 162 MBSubmit: 8432 Solved: 3338[Submit][St ...
- BZOJ 1096: [ZJOI2007]仓库建设(DP+斜率优化)
[ZJOI2007]仓库建设 Description L公司有N个工厂,由高到底分布在一座山上.如图所示,工厂1在山顶,工厂N在山脚.由于这座山处于高原内陆地区(干燥少雨),L公司一般把产品直接堆放在 ...
- 学渣乱搞系列之dp斜率优化
学渣乱搞系列之dp斜率优化 By 狂徒归来 貌似dp的斜率优化一直很难搞啊,尤其是像我这种数学很挫的学渣,压根不懂什么凸包,什么上凸下凸的,哎...说多了都是泪,跟wdd讨论了下,得出一些结论.本文很 ...
- DP斜率优化总结
目录 DP斜率优化总结 任务安排1 任务计划2 任务安排3 百日旅行 DP斜率优化总结 任务安排1 首先引入一道题,先\(O(N^2)\)做法:分别预处理出\(T_i,C_i\)前缀和\(t[i],c ...
- HDU 3507 [Print Article]DP斜率优化
题目大意 给定一个长度为\(n(n \leqslant 500000)\)的数列,将其分割为连续的若干份,使得 $ \sum ((\sum_{i=j}^kC_i) +M) $ 最小.其中\(C_i\) ...
- dp斜率优化
算法-dp斜率优化 前置知识: 凸包 斜率优化很玄学,凭空讲怎么也讲不好,所以放例题. [APIO2014]序列分割 [APIO2014]序列分割 给你一个长度为 \(n\) 的序列 \(a_1,a_ ...
随机推荐
- Codeforces Round #651 (Div. 2) C. Number Game (博弈,数学)
题意:对于正整数\(n\),每次可以选择使它变为\(n-1\)或者\(n/t\) (\(n\ mod\ t=0\)且\(t\)为奇数),当\(n=1\)时便不可以再取,问先手赢还是后手赢. 题解:首先 ...
- 微信小程序swiper实现 句子控app首页滑动卡片
微信小程序swiper实现 句子控app首页滑动卡片 引言:最近看到句子控APP首页的效果很清新,可是发现他的微信小程序端没有实现这个功能,我看了一下难度不大,于是尝试着去实现. 实现效果如下: 1. ...
- springmvc拦截器实现登录验证
首先创建一个实体类: Customer: 1 package com.petcare.pojo.base; 2 3 import java.sql.Date; 4 import java.sql.Ti ...
- codeforces 1000C - Covered Points Count 【差分】
题目:戳这里 题意:给出n个线段,问被1~n个线段覆盖的点分别有多少. 解题思路: 这题很容易想到排序后维护每个端点被覆盖的线段数,关键是端点值不好处理.比较好的做法是用差分的思想,把闭区间的线段改为 ...
- 51nod-1065 最小正子段和 【贪心 + 思维】
N个整数组成的序列a[1],a[2],a[3],-,a[n],从中选出一个子序列(a[i],a[i+1],-a[j]),使这个子序列的和>0,并且这个和是所有和>0的子序列中最小的. 例如 ...
- Java解决Hash(散列)冲突的四种方法--开放地址法(线性探测,二次探测,伪随机探测)、链地址法、再哈希、建立公共溢出区
最近时间有点紧,暂时先放参考链接了,待有时间在总结一下: 查了好多,这几篇博客写的真心好,互有优缺点,大家一个一个看就会明白了: 参考 1. 先看这个明白拉链法(链地址法),这个带源码,很好看懂,只不 ...
- Leetcode(206)-反转链表
反转一个单链表. 示例: 输入: 1->2->3->4->5->NULL 输出: 5->4->3->2->1->NULL 思路:反转链表很简 ...
- Mysql(三)------事务的特性、事务并发、事务读一致性问题
1 什么是数据库的事务? 1.1 事务的典型场景 在项目里面,什么地方会开启事务,或者配置了事务?无论是在方法上加注解,还 是配置切面 <tx:advice id="txAdvice& ...
- 图片转tfrecords
import numpy as np import tensorflow as tf import time import os import cv2 from sklearn.utils impor ...
- 5分钟学Go 基础01:初识 Go 的第一印象是薪水可观
本文首发于公众号「5分钟学Go」,一个让你每次花 5 分钟就能掌握一个技能点的公众号.目前在博主连更 5 分钟学Go系列,大家可以关注下,第一时间掌握Go技能.如果想要加群交流,可以在公众号后台回复「 ...