当前位置: 首页 > news >正文

Slope Trick 学习笔记

洛谷P4597 序列 sequence

这题是 CF13C 的数据加强版。

题面大意

给定一个序列,每次操作可以把某个数 +1 或 −1。求把序列变成非降数列的最小操作次数,\(n\leq 5\times 10^5\)

题解

看到题面不难想到一个非常劣的朴素 dp ,无论如何我们先把能想到的东西写出来,设 \(dp[i][j]\) 为到第\(i\)个数,第 \(i\) 个数的值是 \(j\) 的最小操作次数。

\[dp[i][j]=\min_{0\leq k\leq j}dp[i-1][k]+|a_i-j| \]

考虑优化

对于每一层 \(i\),把 \(j\) 当做 \(x\) 轴,设函数 \(F_i(j)=\min_{0\leq k\leq j}dp[i-1][k],G(j)=|a_i-j|\)

\(i=1\) 时显然有 \(F_1(j)=0\)

引理

  • 凸性相同的几个分段线性函数的每个点函数值加和后的新函数仍然是分段线性函数,且凸性不变。

对本题 \(G(j)\) 是个绝对值函数的特殊情况很容易通过分类讨论归纳证明。

而一般性情况,对于 oier 来说,这种引理是不必要去理解证明的,所以请自行搜索。

显然 \(|a_i-j|\) 是个下凸的分段线性函数,因此我们可以知道对于每一层 \(i\) 的 dp 值都是下凸的,而取 min 操作会使得 \(F(j)\)​ 右半部分被拉平。

\(|a_i-j|\) 的加入会使新函数里 \(a_i\) 左边的线段斜率全部 -1,右边的斜率全部+1。

又可以注意到,函数内相邻的两段线段的斜率不会差很大(因为 \(|a_i-j|\) 的加入最多只会使斜率变化 1)。

所以有个很聪明的办法,我们不存线段,而是存斜率 +1 或 -1 的点,只要我们知道什么地方斜率是 0 以及它的函数值,因为 dp 取凸包的最值肯定从斜率为 0 取,我们就可以维护整个凸包了。

举个栗子

\[F(x)=\begin{cases} -3x+9\ \left\{0\le x\le2\right\}\\ y=1\left\{4\le x\right\}\\ -x+5\left\{2\le x\le4\right\} \end{cases} \]

那么我们要维护的点集就是 \(\{2,2,4\},F(4)=1\)​。

设函数斜率为 \(0\) 直线左端点横坐标为 \(t\)。由于这个下凸(单调不升)的函数的 \(t\) 肯定是点集里x坐标最大的,我们就可以用大根堆维护点集。这时候加入一个 \(|a_i-j|\),如果 \(a_i\geq t\)\(a_i\) 左边斜率全 -1,即插入一个新点,\(t'=a_i\)\(F(t)\) 不变。

如果\(a_i<t\),同样加入点\(a_i\)以表示左边斜率-1,那么斜率为0的部分就会被抬高斜率+1而被舍弃,所以弹出大根堆堆顶,t不在作为斜率拐点而是新最小值的一部分。且对于新的 \(t'\)\(F_i(t')=F_{i-1}(t)+t-a_i\)

code

#include <bits/stdc++.h>
#define int int64_t
//#define int __int128
//#define MOD (1000000007)
//#define eps (1e-6)
#define endl '\n'
#define debug_endl cout<<endl;
#define debug cout<<"debug"<<endl;
using namespace std;
int n,ans;
priority_queue<int> q;
signed main(){//freopen(".in","r",stdin);//freopen(".out","w",stdout);ios::sync_with_stdio(false);cin.tie(0),cout.tie(0);int n;cin>>n;for(int i=1;i<=n;++i){int x;cin>>x;q.emplace(x);//无论如何<ai的斜率-1if(!q.empty()&&x<q.top()){ans+=q.top()-x;//F_{i}(t')=F_{i-1}(t)+t-a_iq.pop();//舍弃掉最右侧斜率拐点,它不再是了q.emplace(x);//右侧斜率全体+1}}cout<<ans;return 0;
}

P4331 [BalticOI 2004] Sequence (Day1)

这个题跟上面的题是一样的,不过你需要一个小 trick 把严格小于变成小于等于。

如果 \(a_i<a_{i+1}\)\(a_i+1\leq a_{i+1}\),又注意到 \(i+1-i=1\) ,所以 \(a_i-i\leq a_{i+1}-(i+1)\)

如果是一般的 dp,我们就会把每个 dp 值都存一下它从哪里转移,假设从 \(k\) 转移来

\[dp[i][j]=dp[i-1][k]+|a_i-j|\\ pre[i][j]=(i-1,k) \]

在 slope trick 里,我们只关心极值点。仍然是那两种讨论,我们可以知道,如果 \(a_i>t\),那么我们是从\(t\)转移而来,应该记录大根堆堆顶;如果 \(a_i<t\),在 dp 意义下其实是从 \(t'\) 自己转移到 \(t'\),所以记录每次的堆顶,并从后往前取 min 即可。

code

#include <bits/stdc++.h>
#define int int64_t
//#define int __int128
//#define MOD (1000000007)
//#define eps (1e-6)
#define endl '\n'
#define debug_endl cout<<endl;
#define debug cout<<"debug"<<endl;
using namespace std;
int n,ans,a[1000010];
priority_queue<int> q;
signed main(){//freopen(".in","r",stdin);//freopen(".out","w",stdout);ios::sync_with_stdio(false);cin.tie(0),cout.tie(0);int n;cin>>n;for(int i=1;i<=n;++i){int x;cin>>x;x-=i;q.emplace(x);if(!q.empty()&&x<q.top()){ans+=q.top()-x;q.pop();q.emplace(x);}a[i]=q.top();}cout<<ans<<endl;for(int i=n-1;i>=1;--i){a[i]=min(a[i+1],a[i]);}for(int i=1;i<=n;++i){cout<<a[i]+i<<endl;}return 0;
}

还有两个例题没写,未完待续……

http://www.jsqmd.com/news/583876/

相关文章:

  • 深圳万象天地街区美陈:如何从空间装饰升级为体验引擎?肆墨设计
  • 基于51单片机的锅炉温控(PT100、热电偶)探秘
  • 针对双SMC控制的四轮转向轨迹跟踪模型优化与效果评估研究
  • 【图像加密】基于 AES算法的图像位平面加密解密算法附Matlab代码
  • 【算法日记】Day 4 一维动态规划基础(按字符DP)
  • 技术创业中的产品迭代:从内核开发到用户中心
  • 创意随笔:智能转录便携终端
  • 告别图层导出烦恼:智能高效的Photoshop批量处理工具如何提升设计效率
  • 折腾光纤模型的手记
  • 智慧城市落地难?这4个痛点,90%项目都踩过
  • 基于[ClaudeCode]源码魔改重建深度集成微信消息桥使微信用户可直接与 Claude 对话,Claude 也可主动向微信用户发送文字、图片和文件
  • MS5540C传感器驱动开发:类SPI协议与校准算法详解
  • LeetCode hot 100 (8-11,自用2026.04.03)
  • Spring Boot注解大赏:40个常用注解助你一臂之力
  • 嵌入式开发中的策略模式应用与优化
  • 探索脊髓损伤与性别差异的统计分析
  • 【数据结构与算法】第24篇:哈夫曼树与哈夫曼编码
  • Cypress是什么
  • 告别token焦虑,Claude Code 本地免费运行
  • 学术研究利器:OpenClaw+千问3.5-9B自动整理文献综述
  • OpenClaw错误处理:gemma-3-12b-it任务失败自动恢复机制
  • 别让APP名字和图标毁了你的Toast!一招教你Android优化技巧
  • 2026-04-02 打卡第 2 天
  • 技术创业中的风险管理:从内核开发到商业稳定
  • JavaScript是什么
  • CSS高频八股
  • 密胺餐具批发新选择:为何河北悦源贸易有限公司成为餐饮采购的可靠伙伴? - 2026年企业推荐榜
  • 百度网盘提取码智能解析:解决资源获取痛点的自动化方案
  • 【数据结构】线索二叉树之中序遍历线索化详解与实现
  • 2026最权威的六大AI论文网站解析与推荐