首页 > 其他分享 >斜率优化

斜率优化

时间:2024-02-14 22:34:33浏览次数:38  
标签:return trn cdot 优化 ll sumt 斜率 sumc

P5785 [SDOI2012]任务安排 为例

朴素方程

其实也没那么简单,第一眼想法是设 \(f(i,k)\) 表示以 \(i\) 为结尾,共分了 \(k\) 段的总方案数

\[f(i,k)=\min_{j=0}^{i-1} f(j,k-1)+(sumc_i-sumc_j)\cdot( sumt_i+s\cdot k) \]

枚举的 \(j\) 表示当前选的第 \(k\) 段为 \((j,i]\)

\(O(n^3)\) 连弱化版也过不去

考虑题中没有指定 \(k\) 这一维,一般可以优化掉

携带代价的思想,发现除了启动时间外,每次时刻恒定,提前计算启动时间对后面的影响,即 \(s\cdot(sumc_n-sumc_j)\)

\[f(i)=\min_{j=0}^{i-1} f(j)+(sumc_i-sumc_j)\cdot sumt_i+s\cdot (sumc_n-sumc_j) \]

\(O(n^2)\),可过 P2365 任务安排


斜率优化

考虑快速找到这个最优的 \(j\),分离含有 \(j\) 的变量

\[f(i)=\min_{j=0}^{i-1} f(j)-(sumt_i+s)\cdot sumc_j+sumc_i\cdot sumt_i+s\cdot sumc_n \]

假设我们已找到那个最有的 \(j\),去掉 \(\min\)

\[f(i)=f(j)-(sumt_i+s)\cdot sumc_j+sumc_i\cdot sumt_i+s\cdot sumc_n \]

移项,

\[f(j)=(sumt_i+s)\cdot sumc_j+f(i)-sumc_i\cdot sumt_i+s\cdot sumc_n \]

我们想最小化的是 \(f(i)\),发现

设 \(y=f(j),k=sumt_i+s\)

\(x=sumc_j,b=f(i)-sumc_i\cdot sumt_i+s\cdot sumc_n\)

则式子写成直线 \(y=kx+b\) 的形式!

\((sumc_i,f(j))\) 看作点,\(k\) 在当前为定值,相当于一条斜率固定直线,从下往上移,\(b\) 不断增大,直到碰到某个点为止

要让 \(b\) 最小,只有下凸壳上的点有贡献,维护下凸壳

同理,要让 \(b\) 最大,只有上凸壳上的点有贡献,维护上凸壳

下凸壳上,每段斜率单调递增

上凸壳上,每段斜率单调递减

我们想找的那个最优的点,查询的斜率 \(k\) 在它左右两段斜率之间


细节

这题没有就这么结束

1. 单调问题

插入点横坐标单调,询问斜率是否单调

  • 如果每次询问的 \(k\) 单调,那最优的就方便查询,用单调队列或单调栈维护,队头斜率比 \(k\) 小弹出,队尾插入,直接查询队头,单调栈同理,一般复杂度 \(O(n)\)

  • 但这题时间 \(t_i\) 可能为负真的离谱,\(k\) 不单调,此时就需要用单调栈维护出整个凸包,在凸包上二分找出这个点,一般复杂度 \(O(n\log n)\)

这还好,但麻烦的是下面的情况

插入点横坐标不单调,询问斜率单调

我们的想法是动态维护这个凸包

但很复杂,要用平衡树且我不会

一般这不是考察的重点,那我们只能曲线救国,转化为横坐标单调的情况

以这题为例,如果 \(c_i<0,t_i>0\)

则倒着 DP,设 \(f(i)\) 表示处理完 \([i,n]\) 的最小费用

\[f(i)=\min_{j=i+1}^n f(j)+(s+sumt_{j-1}-sumt_{i-1})\cdot(sumc_n-sumc_{i-1}) \]

\[f(i)=\min_{j=i+1}^n f(j)+(sumc_n-sumc_{i-1})\cdot sumt_{j-1}+(s-sumt_{i-1})\cdot (sumc_n-sumc_{i-1}) \]

\[f(j)=(-sumc_n+sumc_{i-1})\cdot sumt_{j-1}+f(i)-(s-sumt_{i-1})\cdot (sumc_n-sumc_{i-1}) \]

发现 \(x=sumt_{j-1}\),又是单调的了!

2. 实现问题

这里涉及斜率的比较,如果做除法会除以 0 ,还可能有精度问题

因此这里比较斜率均转化为乘法

但是万一有毒瘤出题人,乘法会爆 long long,还是老老实实的除

再就是一些维护凸包的细节:

  • 凸包中至少有两个点,当只有一个点时不弹出,二分时特判一下

  • 如果两段斜率相同,则先加入的一段也要弹出,防止二分出错

  • 二分实现是找斜率最小的且比 \(k\) 大的一段,注意这个点代表了它后面的一段

补充:

判断凸包可以用叉积,具体的见 计算几何

精度更高,但上下凸壳别弄反了


代码

inline ll merge(ll id)
{
 	ll l = 1, r = top;
 	if(l == r)	return q[r];
	while(l < r)
	{
		ll mid = (l + r) >> 1;
		if((f[q[mid + 1]] - f[q[mid]]) > (s + sumt[id]) * (sumc[q[mid + 1]] - sumc[q[mid]]))	r = mid;
		else	l = mid + 1;
	}
	return q[r];
}
int main()
{
	n = read(), s = read();
	for(reg ll i = 1; i <= n; ++i)
	{
		t[i] = read(), c[i] = read();
		sumt[i] = sumt[i - 1] + t[i], sumc[i] = sumc[i - 1] + c[i];
	}
	memset(f, 0x3f, sizeof(f)), f[0] = 0, q[++top] = 0;
	for(reg ll i = 1; i <= n; ++i)
	{
//		f[i] = min(f[i], f[j] + (sumc[n] - sumc[j]) * s + (sumc[i] - sumc[j]) * sumt[i]);
		ll id = merge(i);
		f[i] = f[id] + (sumc[n] - sumc[id]) * s + (sumc[i] - sumc[id]) * sumt[i];
		while(top > 1 && (f[i] - f[q[top]]) * (sumc[q[top]] - sumc[q[top - 1]]) <= (f[q[top]] - f[q[top - 1]]) * (sumc[i] - sumc[q[top]]))
			--top;
		q[++top] = i;	  
	}
	printf("%lld", f[n]);
	return 0;
}

更多应用

P3195 [HNOI2008] 玩具装箱

P4360 [CEOI2004] 锯木厂选址

拆开括号,推式子后就是板子,由于斜率单调,放单调队列的板子:

// P3195 [HNOI2008] 玩具装箱
inline ll sqr(ll p)	{return p * p;}
inline ll x(ll p)	{return p + s[p];}
inline ll y(ll p)	{return f[p] + sqr(x(p));}
inline double slope(ll a, ll b)
{
	if(x(a) == x(b))	return -inf;
	return (double)(y(a) - y(b)) * (double)1.0 / (x(a) - x(b));
} 
int main()
{
	n = read(), l = read();
	for(ll i = 1; i <= n; ++i)	c[i] = read(), s[i] = s[i - 1] + c[i];
	q[++tt] = 0;
	for(ll i = 1; i <= n; ++i)
	{
		while(hh < tt && slope(q[hh + 1], q[hh]) <= (double)2.0 * (i + s[i] - l - 1))	++hh;
		f[i] = f[q[hh]] + sqr(i + s[i] - l - 1 - x(q[hh]));
		while(hh < tt && slope(q[tt], q[tt - 1]) >= slope(i, q[tt]))	--tt;
		q[++tt] = i;
	}
	printf("%lld", f[n]);
	return 0;
}

P5468 [NOI2019] 回家路线

发现时间一定是从前往后,把每条路线拆成发车和结束两个,发车时计算,结束时加入决策集合

设 \(f(i)\) 为走完第 \(i\) 条线路后的最小烦躁值,最后把总用时的部分加上

新增从 \(1\to 1\),起止时间均为 \(0\) 的路线,方便转移

看到二次式,拆开,推式子

\[f_i=\min_{j}f_j+A(p_i-q_j)^2+B(p_i-q_j)+C \]

\[f_i=f_j+Ap_i^2+Aq_j^2+2Ap_iq_j+Bp_i-Bq_j+C \]

\[f_i=f_j+Aq_j^2-Bq_j+2Ap_iq_j+Ap_i^2+Bp_i+C \]

设 \(b=f_i,y=f_j+Aq_j^2-Bq_j,k=2Ap_i,x=q_j\)

则刚好写成斜率优化的形式!

注意线路端点必须相接,由于按时间排序后斜率单调递增,因此用 deque 存储,把每个插入进终点站对应的队列中

我不知道小猫烦不烦燥,反正我调试时挺烦躁的

ll y(ll i)	{return f[i] + a * trn[i].q * trn[i].q - b * trn[i].q;}
pll mins(ll i, ll j)	{return mp(trn[i].q - trn[j].q, y(i) - y(j));}
ll cross(pll i, pll j)	{return i.fi * j.se - i.se * j.fi;}
void ins(ll nw, ll i)
{
	if(f[i] > 1e18)	return;
	while(que[nw].size() > 1)
	{
		ll t1 = que[nw].back();	que[nw].popb();	ll t2 = que[nw].back();
		if(cross(mins(i, t1), mins(i, t2)) < 0)	{que[nw].pb(t1);	break;}
	}
	que[nw].pb(i);
}
void calc(ll i)
{
	if(que[trn[i].st].empty())	return;
	ll nw = trn[i].st;
	while(que[nw].size() > 1)
	{
		ll t1 = que[nw].front();	que[nw].popf();	ll t2 = que[nw].front();
		if(cross(mins(t2, t1), mp(1, 2ll * a * trn[i].p)) < 0)	{que[nw].pf(t1);	break;}
	}
	ll pos = que[nw].front();
	f[i] = y(pos) - 2ll * a * trn[i].p * trn[pos].q + c + a * trn[i].p * trn[i].p + b * trn[i].p;
}
int main()
{
	n = read(), m = read(), a = read(), b = read(), c = read();
	trn[1].st = trn[1].ed = 1, trn[1].p = trn[1].q = 0;
	for(int i = 2; i <= m + 1; ++i)	f[i] = inf;
	f[1] = 0;
	ins(trn[1].ed, 1);
	for(int i = 2; i <= m + 1; ++i)
	{
		trn[i].st = read(), trn[i].ed = read(), trn[i].p = read(), trn[i].q = read();
		tim[trn[i].p].pb(mp(i, 0)), tim[trn[i].q].pb(mp(i, 1));
	}
	for(ll i = 0; i <= 40000; ++i)
	{
		for(pll j : tim[i])	
			if(j.se)	ins(trn[j.fi].ed, j.fi);
		for(pll j : tim[i])
			if(!j.se)	calc(j.fi); 
	}
	for(int i = 1; i <= m + 1; ++i)
		if(trn[i].ed == n)	ans = min(ans, f[i] + trn[i].q);
	printf("%lld", ans);
	return 0;
}

P5504 [JSOI2011] 柠檬

乍一看指定了区间,贡献也只能 \(O(n)\) 算

但是可以排除一定不优的情况:

如果这个区间选择 \(x\),且区间不以 \(x\) 为开头结尾,那么把区间不是 \(x\) 的开头结尾去掉,这个区间的贡献不变,且会产生新增贡献,比原来更优

因此,取的数一定是区间的开头结尾

那么与上题处理位置的方法一样,插入至当前的后面一个数对应的 deque,查询则在当前的数对应的 deque 中查询

ll x(ll i)	{return s[i][1];}
ll y(ll i)	{return f[i] + a[i + 1] * s[i][1] * s[i][1];}
pll mins(ll i, ll j)	{return mp(x(i) - x(j), y(i) - y(j));} 
ll dot(pll i, pll j)	{return i.fi * j.fi + i.se * j.se;}
ll cross(pll i, pll j)	{return i.fi * j.se - i.se * j.fi;}
void calc(ll i)
{
	ll id = a[i], k = 2ll * a[i] * s[i][0];
	while(q[id].size() > 1 && cross(mins(q[id].back(), q[id][q[id].size() - 2]), mp(1, k)) >= 0)	
		q[a[i]].popb();
	f[i] = y(q[id].back()) - k * x(q[id].back()) + a[i] * s[i][0] * s[i][0];
}
void ins(ll i)
{
	ll id = a[i + 1];
	while(q[id].size() > 1 && cross(mins(i, q[id].back()), mins(q[id].back(), q[id][q[id].size() - 2])) <= 0)
		q[id].popb();
	q[id].pb(i);
}
int main()
{
	n = read();
	for(int i = 1; i <= n; ++i)	a[i] = read();
	for(int i = 1; i <= n; ++i)
	{
		++buc[a[i]];
		s[i][0] = buc[a[i]], s[i][1] = buc[a[i + 1]];
	}
	q[a[1]].pb(0);
	for(int i = 1; i <= n; ++i)
	{
		if(q[a[i]].empty())	continue;
		calc(i);
		if(i < n)	ins(i);
	}
	printf("%lld", f[n]);
	return 0;
} 

标签:return,trn,cdot,优化,ll,sumt,斜率,sumc
From: https://www.cnblogs.com/KellyWLJ/p/18015726

相关文章

  • 电子商务发货流程如何优化?发货单和快递单如何匹配?
     目前采用的是快递单打印订单备注的问题解决打包问题,但是填写备注的工作量巨大还容易出错,现在成熟的解决方案是什么样的? 电子商务的发货流程如下:库存组依据账物组交给的销售定单进行配货,配货结束在配货单上签字确认后交给发货组。发货组接到配货组交给的物品后依据销售定单......
  • GDI+性能优化
    每个Windows控件都可以拥有一个paint事件处理程序和一个表示此控件是绘图画布的Graphics对象。这意味着我们可以使用一个按钮或一个列表框作为绘图画布。如果在菜单或按钮的Click事件处理程序中绘制图形对象,则必须最后调用 this.Invalidate()方法。如果不调用,窗体将不......
  • Python 机器学习 线性回归 梯度下降法优化损失函数
    ​ Python机器学习中,梯度下降法是一种用于优化线性回归模型(以及其他机器学习算法)的损失函数的通用算法。目的是通过迭代地调整模型的参数(权重和截距),以最小化损失函数,例如均方误差(MSE)。梯度下降的基本思想是计算损失函数相对于每个参数的梯度(即偏导数),然后朝着减少损失的方向调......
  • Python 机器学习 线性回归 正规方程优化损失函数
    ​Python机器学习中,线性回归模型的参数可以通过正规方程(NormalEquation)直接计算得到,无需使用迭代优化算法如梯度下降。正规方程提供了一种找到成本函数最小值的解析解,从而直接计算出模型参数(系数和截距)。正规方程是一种简单有效的方法,可以用于求解线性回归模型的参数。其优点是......
  • 神经网络包nn和优化器optm
    torch.nn是专门为神经网络设计的模块化接口。nn构建于Autograd之上,可用来定义和运行神经网络。这里我们主要介绍几个一些常用的类除了nn别名以外,我们还引用了nn.functional,这个包中包含了神经网络中使用的一些常用函数,这些函数的特点是,不具有可学习的参数(如ReLU,pool,DropOut等)......
  • MySQL优化
    优化分为六大部分:SQL语句的优化索引的优化表结构的优化事务优化系统配置优化物理机的优化SQL语句的优化a.尽量使用select字段名,不要使用select*,select*不能使用索引覆盖。只查需要用到的列。b.小表驱动大表。主查询in/exists子查询.ⅰ.in先执行右边的子查询......
  • 单调队列优化DP&斜率优化&四边形不等式
    在本文中,我们将通过一道题来浅谈DP优化三大妈。P3195[HNOI2008]玩具装箱-洛谷|计算机科学教育新生态(luogu.com.cn)对于这种类型的题目,我们一般可以转化为如下形式:那么,$val(i,j)$又通常分为两种情况:其值仅与$i,j$中的一个有关。其值与$i,j$两者都有关。单调队列......
  • java基础语法之匿名内部类的优化格式lambda
    一:lambda表达式的概述lambda表达式实质上就是对匿内部类的优化但是又不同于匿名内部类。它的使用前提是有且仅有一个抽象方法,有一个接口。二:具体说明<1>函数式编程思想的介绍在数学中,函数就是有输入量、输出量的一套计算方案,也就是“拿数据做操作”在数学中,函数就是有输......
  • 利用ThreadLocal优化获取用户基本信息
    //测试类packagecom.di.bigevent;importorg.junit.jupiter.api.Test;publicclassThreadLocalTest{@TestpublicvoidtestThreadLocalSetAndGet(){ThreadLocaltl=newThreadLocal();newThread(()->{tl.set("李星......
  • 建站之关于CP网站SSC搭建BC平台建站建议和运营优化分享
    关于搭建BC平台建站建议和运营优化分享,我可以在一定程度上提供一些信息和经验。一、关于搭建BC平台建站建议:确定网站目标和受众群体:在开始构建网站之前,需要明确网站的定位和目标受众群体。这将有助于确保网站内容符合受众需求,提高转化率。选择合适的开发语言和技术框架:根据网站......