Slope Trick 总结

Slope Trick 总结

Slope Trick

Slope Trick 用于维护凸性的分段一次函数,且每一段一次函数的斜率满足为整数且级大小为 \(O(n)\)

使用 Slope Trick 可以方便地求函数的最值、对后缀取最值、给全局加上一次函数或绝对值函数,要保证操作前后函数都为凸性

算法概述

以下我们默认函数为下凸,即斜率递增。我们首先维护一个起始点 \(x_0\)\(f(x_0)\) 以及往后一段直线的 \(k_0\)

然后如果之后在 \(x\) 处往后斜率增加了 \(\Delta k\),那么往数据结构(堆)里插入 \(\Delta k\)\(x\)。那么就有以下操作。

  • 求最值:找到数据结构中 \(k=0\)\(x\),则这个 \(x\) 就是函数上的最值。
  • 全局加直线:对 \(k_0\) 加上斜率,并维护 \(f(x_0)\) 即可。
  • 全局加绝对值函数:对 \(k_0\) 加上斜率,并维护 \(f(x_0)\),同时往数据结构中插入绝对值函数的断点。
  • 对后缀取最小值:把 \(k=0\) 以后的点删掉即可。

P4597 序列 sequence - 洛谷

\(f_{i,j}\) 表示第 \(i\) 个位置选了 \(j\) 的最小操作数,有如下转移:

\[f_{i,j}=\min _{k\le j}\{f_{i-1,{k}}+|a_i-j|\} \]

那么每次加上绝对值函数,然后把后缀取最小值。

思考一下可以发现,具体实现上就是先往堆里插入两个 \(a_i\),然后弹掉一个堆顶。

最后求出 \(x\) 后,可以遍历一遍函数求值。实现:

int n;
int a[N],f[N];
int f0,k0;
priority_queue<int> q;
int b[N],c[N];
signed main(){
	read(n);
	fo(i,1,n) read(a[i]),b[i]=a[i];
	sort(b+1,b+1+n);
	int len=unique(b+1,b+1+n)-b-1;
	fo(i,1,n) c[i]=lower_bound(b+1,b+1+len,a[i])-b;
	f0=a[1]-b[1],k0=-1;
	q.push(c[1]);
	fo(i,2,n) {
		f0+=a[i]-b[1],k0--;
		q.push(c[i]),q.push(c[i]);
		while(Size(q)>-k0) q.pop();
	}
	vi t; while(Size(q)) t.pb(q.top()),q.pop();
	int at=b[1];
	fd(i,Size(t)-1,0) {
		f0+=(b[t[i]]-at)*k0;
		++k0,at=b[t[i]];
	}
	write(f0);
	return 0;
}

P4331 [BalticOI 2004] Sequence 数字序列 - 洛谷

首先递增很难搞,有一个技巧,我们开始先把 \(a_i\) 全部减去 \(i\),这样就转化为了不下降的问题,最后输出时再加上 \(i\) 即可。

还是设 \(f_{i,j}\) 为第 \(i\) 数选了 \(j\),转移同理。那么我们要输出方案怎么办呢,我们考虑记录每个 \(i\) 操作完后最值 \(x\)\(c_i\)

那么如果 \(c_i\le c_{i+1}\) 显然 \(f_{i,c_i}\) 可以转移到 \(f_{i+1,c_{i+1}}\)。否则由于函数是凸的,所以最优情况下是第 \(i\) 次选 \(f_{i,c_{i+1}}\) 转移到第 \(f_{i+1,c_{i+1}}\)

所以我们对 \(c_i\) 求后缀 \(\min\) 得到的数组就是 \(b_i\) 了。实现如下:

const int N=1e6+5;
int n,a[N],b[N];
int f0,k0;
priority_queue<int> q;
signed main(){
	read(n);
	fo(i,1,n) read(a[i]),a[i]-=i;
	k0=-1,f0=a[1]+N;
	q.push(a[1]);
	b[1]=a[1];
	fo(i,2,n) {
		k0--,f0+=a[i]+N;
		q.push(a[i]),q.push(a[i]);
		q.pop();
		b[i]=q.top();
	}
	int res=abs(a[n]-b[n]);
	fd(i,n-1,1) b[i]=min(b[i+1],b[i]),res+=abs(a[i]-b[i]);
	write(res,'\n');
	fo(i,1,n) write(b[i]+i,' ');
	return 0;
}

P3642 [APIO2016] 烟花表演 - 洛谷

\(f_i(x)\) 表示点 \(i\) 子树内的烟花从 \(i\) 开始还需要 \(x\) 秒同时爆炸的最小代价。

假设我们已经求出了 \(f_i\),考虑 \(i\) 前面再新增一条原长 \(val\) 的导线 \(f\) 会怎么改变,那么有如下转移:

\[f'(x)\gets\min_{w\ge 0}\{f(x-w)+|w-val|\} \]

\([L,R]\)\(f_i\) 中斜率为 \(0\) 的那一段(即取最小值的那一段),那么有结论:

\[f'(x)=\begin{cases}f(x)+val & x<L\\ f(L)+(L+val)-x & L\le x<L+val \\ f(L) & L+val\le x\le R+val \\ f(L)+x-(R+val) & x>R+val\end{cases} \]

解释一下。对于第一条,发现当 \(w=0\) 时最优,因为前面的点一定比 \(x\) 的点更高。
对于第二条,即从 \(L\) 开始到 \(L+val\) 的斜率为 \(-1\) 的直线,因为此时令 \(x-w=L\) 时最优。
对于第三条,令 \(w=val\) 即可从 \([L,R]\) 转移过来。
对于第四条,与第二条同理,是从 \(R+val\) 开始的斜率为 \(1\) 的直线。

求出增加一条导线后的 \(f'\) 后,把一个点所有儿子的 \(f'\) 合并即可得到当前点的 \(f\)。****

使用可并堆实现,时间复杂度 \(O(n\log n)\)。参考代码:

const int N=3e5+5,Q=6e6;
#define int ll
int n,m;
vp G[N];
int lc[Q],rc[Q],dis[Q],val[Q],sz[Q],tot;
void maintain(int x) {
	if(dis[lc[x]]<dis[rc[x]]) swap(lc[x],rc[x]);
	dis[x]=dis[rc[x]]+1;
	sz[x]=1+sz[lc[x]]+sz[rc[x]];
}
int merge(int x,int y) {
	if(!x||!y) return x|y;
	if(val[x]<val[y]) swap(x,y);
	rc[x]=merge(rc[x],y);
	maintain(x);
	return x;
}
void push(int &x,int y) {val[++tot]=y,sz[tot]=1; x=merge(x,tot);}
int top(int x) {return val[x];}
void pop(int &x) {x=merge(lc[x],rc[x]);}
void Print(int x) {
	cerr<<val[x]<<' ';
	assert(dis[lc[x]]>=dis[rc[x]]);
	if(lc[x]) Print(lc[x]);
	if(rc[x]) Print(rc[x]);
}
void print(int x) {
	Print(x), cerr<<'\n';
}
int rt[N];
int L[N],R[N],B[N],K[N];
void dfs(int x) {
	for(auto [v,w]:G[x]) {
		if(v>n) {
			L[v]=R[v]=B[v]=w;
			K[v]=-1;
			push(rt[v],w),push(rt[v],w);
		}
		else {
			dfs(v);
			while(sz[rt[v]]>-K[v]) R[v]=top(rt[v]),pop(rt[v]);
			L[v]=top(rt[v]);
			B[v]+=w, pop(rt[v]);
			push(rt[v],L[v]+w),push(rt[v],R[v]+w);
		}
		rt[x]=merge(rt[x],rt[v]),K[x]+=K[v],B[x]+=B[v];
	}
}
signed main(){
	read(n,m);
	fo(i,2,n+m) {
		int u,v; read(u,v);
		G[u].pb({i,v});
	}
	dfs(1);
	while(sz[rt[1]]>-K[1]) pop(rt[1]);
	vi t; while(sz[rt[1]]) t.pb(top(rt[1])),pop(rt[1]);
	int at=0;
	fd(i,Size(t)-1,0) {
		B[1]+=(t[i]-at)*K[1];
		at=t[i],++K[1];
	} 
	write(B[1],'\n');
	return 0;
}

鸣谢-参考资料

Slope Trick 总结 - rui_er - 博客园

posted @ 2025-05-21 22:16  dengchengyu  阅读(163)  评论(0)    收藏  举报