斜率优化学习笔记
本来写了一个,但是 U 盘没了,哈哈。
凸壳
分为上凸壳和下凸壳。
凸包/凸壳:包含给定点集的最小凸多边形。
一个凸壳由一个上凸壳和一个下凸壳组成。
上凸壳就是相邻边的斜率逐渐变小的部分。

下凸壳就是相邻边的斜率逐渐变大的部分。

非常形象。
斜率优化
主要优化一个形如 \(f_{i}=\min/\max\limits(f_j+A(i)+B(j)+C(i)D(j))\) 的转移式。
注意贡献必须形如 \(C(i)D(j)\) 不是 \(C(ij)\) 这种和 \(ij\) 有关而且不相乘的。
假设现在有两个决策点 \(j_1,j_2\)。考虑 \(j_1\) 什么时候比 \(j_2\) 优。以下推导假设转移式取 \(\min\)。
为了方便,假设 \(D(j_1)<D(j_2)\)。化简一下,得到:
发现若将一个决策点 \(j\) 看作一个二维平面上的点 \((X_j,Y_j)\),并令 \(X_j=D(j),Y_j=f_j+B(j)\)。则左式的含义就是过点 \(j_1\) 和 点 \(j_2\) 的直线的斜率,若这个斜率 \(>-C(i)\) 则 \(j_1\) 比 \(j_2\) 优。
这启发我们维护这些线。
令 \(k=-C(i)\)。
考虑现在有三个点,它们形成了上凸壳。

分类讨论一下 \(k\) 的取值,发现中间的那个点无论如何都不会取到。
于是此时我们可以将它直接去掉,将剩下两个点相连。
不难发现此时我们实际维护的就是下凸壳。
回归问题,现在已经维护了一个下凸壳,考虑现在如何找一个最优的点。
因为下凸壳的线段斜率递增,即找一个点使得它左边的线段斜率 \(<k\),右边的线段斜率 \(>k\)。
特殊情况是 \(X(j_1)=X(j_2)\),不难发现对每个 \(X\) 保留 \(Y\) 最小的最优。
这个问题的处理分为:
-
\(X(j)\) 有单调性,相当于每次在末尾/开头添加一个点,对于询问直接二分即可。
-
\(k,X(j)\) 有单调性,即选择的点还是有单调性的。直接暴力跳,每次把不优的点直接删掉复杂度就是对的,即单调队列。
-
没有单调性。用一个 set 先找到 \(X(j)\) 插入的前驱后继,然后李超线段树暴力插入线段也是可以的,询问在线段树上二分,不过复杂度会多一个 \(\log\)。
CF1715E Long Way Home
因为是做到这道题重新写的斜优笔记,所以拿这题当例题。
设 \(f_{u,i}\) 表示点 \(u\),进行了 \(i\) 次瞬移的最短路。
转移分为瞬移和不瞬移。
后者每次跑一遍最短路即可。考虑第一种转移,显然可以斜率优化。
即 \(X(v)=v,Y(v)=f_v+v^2\)。若 \(X(v1)<X(v2)\) 且 \(v1\) 比 \(v2\) 更优则:
这题 \(X\) 单增 \(k\) 也单增,单调队列即可。
其实 \(X\) 不单增也能做,因为这题转移是先把所有决策点扔进去再做的。
每次先进行一遍这个 dp 然后扔进去跑一遍最短路。
这个有个细节,不能直接跑 \(1\) 的单源最短路。因为可能存在 \(u\) 在这个斜优 dp 跑完之后更新了其值之后在跑最短路的时候没有遍历到这个点导致其邻域没有被更新到。
于是我们考虑在每次有点更新最短路值的时候就给他也塞队列里。
#include<bits/stdc++.h>
#define sd std::
#define int long long
#define F(i,a,b) for(int i=(a);i<=(b);i++)
#define ff(i,a,b) for(int i=(a);i>=(b);i--)
#define me(x,y) memset(x,y,sizeof x)
#define pii sd pair<int,int>
#define X first
#define Y second
#define x(i) i
#define y(i) (f[i][k-1]+i*i)
#define dbg(x) sd cout<<#x<<":"<<x<<" "
#define dg(x) sd cout<<#x<<":"<<x<<"\n"
#define inf 2e10
int read(){int w=1,c=0;char ch=getchar();for(;ch>'9'||ch<'0';ch=getchar()) if(ch=='-') w=-1;for(;ch>='0'&&ch<='9';ch=getchar()) c=(c<<3)+(c<<1)+ch-48;return w*c;}
void printt(int x){if(x>9) printt(x/10);putchar(x%10+48);}
void print(int x){if(x<0) putchar('-'),printt(-x);else printt(x);}
void put(int x){print(x);putchar('\n');}
void printk(int x){print(x);putchar(' ');}
const int N=2e5+10,P=1e9+7;
int n,m,K,k,f[N][23],vis[N];
sd vector<pii> G[N];
int slope(int a,int b)
{
return (y(a)-y(b))/(x(a)-x(b));
}
struct node
{
int u,d;
bool operator <(const node& b)const
{
return d>b.d;
}
node(int uu,int dd)
{
u=uu,d=dd;
}
};
sd priority_queue<node> q;
void dij()
{
F(i,1,n) vis[i]=0;
while(!q.empty())
{
auto [u,d]=q.top();q.pop();
if(vis[u]) continue;
vis[u]=1;
for(auto [v,w]:G[u]) if(f[v][k]>f[u][k]+w)
{
f[v][k]=f[u][k]+w;
q.push(node(v,f[v][k]));
}
}
}
int st[N],top;
void solve()
{
n=read(),m=read(),K=read();
F(i,1,m)
{
int x=read(),y=read(),z=read();
G[x].emplace_back(y,z);
G[y].emplace_back(x,z);
}
f[1][0]=0;
F(i,2,n) f[i][0]=inf;
q.push(node(1,0));
dij();//跑出来k=0的结果
for(k=1;k<=K;k++)
{
F(i,1,n) f[i][k]=inf;
top=0;
F(u,1,n)
{
while(top>1&&slope(st[top],u)<slope(st[top],st[top-1])) top--;//删去上凸壳的点
st[++top]=u;
}
int i=1;//目前的决策点
F(u,1,n)
{
while(i<top&&slope(st[i],st[i+1])<2*u) i++;//如果斜率<2u就一直往后面跳
int v=st[i];
if(f[u][k]>f[v][k-1]+(u-v)*(u-v))
{
f[u][k]=f[v][k-1]+(u-v)*(u-v);
q.push(node(u,f[u][k]));
}
}
dij();
}
F(i,1,n) printk(f[i][K]);
}
signed main()
{
// freopen(".in","r",stdin);
// freopen(".out","w",stdout);
int T=1;
// T=read();
while(T--) solve();
return 0;
}

浙公网安备 33010602011771号