浅谈斜率优化 DP

由于作者去年 tg 都没一等,文中有错误请大家多多提出。

斜率优化是用来求解一系列 dp 方程形如 fi=minj=1i{fj+ai+bj+xi×yj}(其中 ai 表示只和 i 有关的项,bj 为只和 j 有关的项,xi×yj 表示 i 有关的项和 j 有关的项相乘得到的项)。

显然转移方程中 min 换为 max 也同理。

考虑把转移方程化简,得到 fi=minj=1i{fj+bj+xi×yj}+ai,此时,我们把上式与一次函数的截距联系起来,令 Y=fj+bj,k=xi,X=yj,此时方程即可化为 fi=minj=1i{YkX}+ai

此时,根据一次函数定义,可以得到 YkX 表示的是经过经过点 (X,Y) 的斜率为 k 的直线的截距。所以我们只需要将斜率为 k 的直线从下往上(如果是 max 就从上往下)贴合决策点,第一次经过的决策点即为更新 fi 最优的点。

通过观察以下图,可以发现中间的点 BE 一定不会被任一斜率的最优直线经过,只有在下凸壳上的点才有可能对答案有贡献(大家别被下凸壳给吓到了,这里其实只是一个名词,与实际代码没有关系)。

20240728.png

如果 k,x 均对于 i 单调递增,那么那么可以用单调队列实现,如果仅 x 递增,那么可以用二分实现,其他情况可以用平衡树/CDQ分治等算法优化,这里不展开(主要是作者根本不会 23333)。

模板

还是上式转移方程对应的代码,这里给出 k,x 均对于 i 递增的做法:

int f[N],q[N];
int hh = 0,tt = -1;
q[++tt] = 0;
// 这里 Y (i),X (i),K (i) 就是上式中推出的式子中的 Y,X,k
double slope (int i,int j) {
    return (Y (j) - Y (i)) / (X (j) - X (i));
}
for (int i = 1;i <= n;i++) {
    while (hh < tt && slope (q[hh],q[hh + 1]) < K (i)) hh++;
    f[i] = f[q[hh]] + a[i] + b[q[hh]] + x[i] * y[q[hh]]; //转移方程
    while (hh < tt && slope (q[tt - 1],q[tt]) > slope (q[tt],i)) tt--;
    q[++tt] = i;
}

P2120

这里不选取任务安排系列的题目原因是非斜率优化部分过于麻烦。

下文中 di 即为原题面中的 xi

考虑列出暴力转移方程 fi=minj=0i1{fj+ci+k=j+1ipk×(didk)}。(这里 k 的范围为 i 其实会好写一些,后面有解释)

展开,得到 =minj=0i1{fj+ci+(k=j+1ipk×di)(k=j+1ipk×dk)}

继续化简得到 =minj=0i1{fj+ci+di×(k=j+1ipk)(k=j+1ipk×dk)}

g1,i=j=1ipjg2,i=j=1i(pj×dj)

那么得到 =minj=0i1{fj+ci+di×(g1,ig1,j)(g2,ig2,j)}

展开并添加括号得到 =minj=0i1{fj+g2,jdi×g1,j}+ci+di×g1,ig2,i

Y=fj+g2,j,k=di,X=g1,j,那么 min 里的项就是 minj=0i1{YkX},显然可以用前面的式子进行优化。

注意到 k 关于 i 递增而递增,所以我们可以用单调队列维护下凸壳。

下面给出核心代码:

LL Y (int i) {
	return f[i] + g2[i];
}
LL X (int i) {
	return g1[i];
}
LL K (int i) {
	return x[i];
}
double slope (int i,int j) {
	if (X (i) == X (j)) return Y (j) - Y (i) > 0 ? 9e18 : -9e18;	// 特判斜率正负无穷大
	return (Y (j) - Y (i)) / (X (j) - X (i));
}

x[++n] = 3e9;	// 添加哨兵,这题数据比较恶心
for (int i = 1;i <= n;i++) g1[i] = g1[i - 1] + p[i],g2[i] = g2[i - 1] + p[i] * x[i]; 
int hh = 0,tt = -1;
q[++tt] = 0;
for (int i = 1;i <= n;i++) {
	while (hh < tt && slope (q[hh],q[hh + 1]) < K (i)) hh++;
	f[i] = f[q[hh]] + x[i] * (g1[i] - g1[q[hh]]) - (g2[i] - g2[q[hh]]) + c[i];
//	cout << q[hh] << endl;
	while (hh < tt && slope (q[tt - 1],q[tt]) > slope (q[tt],i)) tt--;
	q[++tt] = i;
}

简单总结一下,我们对于上述类似的转移方程,我们设 Y 等于仅和 j 有关的项,k 等于有关 i 的项和有关 j 的项相乘的项中的 i 的项(就是 xi×yj 中的 xi),X 等于有关 i 的项和有关 j 的项相乘的项中的 j 的项(就是 xi×yj 中的 yj)。

这样就讲完了最最最模板的斜率优化了。

求个赞不过分吧 QaQ

posted @   incra  阅读(19)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 【译】Visual Studio 中新的强大生产力特性
· 【设计模式】告别冗长if-else语句:使用策略模式优化代码结构
· AI与.NET技术实操系列(六):基于图像分类模型对图像进行分类
点击右上角即可分享
微信分享提示