斜率优化

斜率优化

对于形如\(f_i=min\left \{i+j+a_i+a_j\right \}\)的DP进行优化,使其时间复杂度降低一个\(n\)

模板

luogu 2365

题目大意

有n个任务,每个任务有一个时间和费用系数,每批任务可以选择若干个任务,每处理一批任务之前有s的开机时间,这批任务所需时间即为所选任务的时间之和,且所选任务一起完成
对于第i个任务,其费用为完成时间乘费用系数
让你安排任务,使其费用总和最小

解题思路

对于该题,我们可以先列出朴素的DP方程(t为时间前缀和,c为费用系数前缀和)

\[\begin{aligned}f_i&=min_{j=1}^{i-1}(f_j+S\times(c_n-c_j) + t_i\times(c_i-c_j))\\ &=min_{j=1}^{i-1}(f_j+S\times c_n-S\times c_j + t_i\times c_i- t_i \times c_j))\end{aligned}\]

对于a>b
若a对i的贡献>b对i的贡献
那么有:

\[\begin{aligned}f_a+S\times c_n-S\times c_a + t_i\times c_i- t_i \times c_a&\leqslant f_b+S\times c_n-S\times c_b + t_i\times c_i- t_i \times c_b\\ f_a-S\times c_a - t_i \times c_a&\leqslant f_b-S\times c_b - t_i \times c_b\\ f_a-f_b&\leqslant S\times c_a + t_i \times c_a-S\times c_b - t_i \times c_b\\ f_a-f_b&\leqslant (S+t_i)\times(c_a-c_b)\\ (f_a-f_b)/(c_a-c_b)&\leqslant s+t_i\end{aligned}\]

那么如果满足上式就可以得到“a对i的贡献>b对i的贡献”
上述式子和斜率十分相像
我们把f看作y,把c看作x那么就得到了一张关于相邻的点连线的图(k为斜率)

对于该图,\(k1>k2\)
对于某个i,若C为最优决策点,那么\(k1\leqslant s+t_i,k2>s+t_i\)
所以\(k1\leqslant s+t_i< k2\),与\(k1>k2\)不符,所以C为最优决策点不成立,所以把C给移除掉
由此可以得到:
我们要维护的是连续上升的k,也就是一个下凸的图,使得所有点都可能有用
维护这样一个图,只需在每次插入一个点时,拿i和i-1的连线与i-1和i-2的连线比较斜率,如果i和i-1的连线斜率较小,那么把i-1移去
对于决策点的选择,因为\(s+t_i\)是单调递增的,当第一条线斜率小于等于\(s+t_i\),就把第一个点和线给移除掉,这样移除后得到的所有线斜率都小于\(s+t_i\)所以第一个点就是最优决策点

代码

#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#define ll long long
#define N 5010 
using namespace std;
int n, s, l, r, x[N], y[N], f[N], q[N], t[N], c[N];
int main()
{
	scanf("%d%d", &n, &s);
	for (int i = 1; i <= n; ++i)
	{
		scanf("%d%d", &t[i], &c[i]);
		t[i] += t[i - 1];//前缀和
		c[i] += c[i - 1];
	}
	memset(f, 127/3, sizeof(f));
	f[0] = 0;//0点作为第一个点
	q[1] = 0; 
	l = 1;
	r = 1;
	for (int i = 1; i <= n; ++i)
	{
		while(l < r && y[q[l + 1]] - y[q[l]] <= (s + t[i]) * (x[q[l + 1]] - x[q[l]])) l++;//把前面已经大于s*t的删掉
		f[i] = f[q[l]] + s * (c[n] - c[q[l]]) + t[i] * (c[i] - c[q[l]]);//计算
		x[i] = c[i];
		y[i] = f[i];
		while(l < r && (y[i] - y[q[r]]) * (x[q[r]] - x[q[r - 1]]) <= (y[q[r]] - y[q[r - 1]]) * (x[i] - x[q[r]]))//把不可能为决策点的点给删掉
			r--;
		q[++r] = i;//把当前点插进去
	}
	printf("%d", f[n]);
	return 0;
}

例题

推荐题单

https://www.luogu.com.cn/training/5352#problems

例题1

锯木厂选址
先根据题意得到转移方程,然后解该方程,直接斜率优化DP即可(就是一道模板题,主要就是解方程不要出错)
代码

#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#define ll long long
#define N 20010
using namespace std;
ll n, l, r, sum, ans, q[N], w[N], d[N], y[N];
int main()
{
	scanf("%lld", &n);
	for (int i = 1; i <= n; ++i)
		scanf("%lld%lld", &w[i], &d[i]);
	for (int i = n - 1; i > 0; --i)
		d[i] += d[i + 1];
	for (int i = 1; i <= n; ++i)
		sum += w[i] * d[i], w[i] += w[i - 1];
	ans = sum;
	for (int i = 1; i <= n; ++i)
	{
		while(l < r && (y[q[l + 1]] - y[q[l]]) > d[i] * (w[q[l + 1]] - w[q[l]])) l++;
		ans = min(ans, sum - w[q[l]] * d[q[l]] - (w[i] - w[q[l]]) * d[i]);//找一个最优的结果
		y[i] = w[i] * d[i];
		while(l < r && (y[i] - y[q[r]]) * (w[q[r]] - w[q[r - 1]]) > (y[q[r]] - y[q[r - 1]]) * (w[i] - w[q[r]])) r--;
		q[++r] = i;
	}
	printf("%lld", ans);
	return 0;
}

例题2

任务安排
因为t(前缀和)不具有单调性,所以对左端点不能删去,取最优决策点时要二分查找

#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#define ll long long
using namespace std;
ll n, s, t, c, l, r, h, x[300010], y[300010], f[300010], q[300010], sumt[300010], sumc[300010];
ll find(ll ti)
{
	ll L = 1, R = r;
	while (L < R)//二分
	{
		ll mid = (L + R + 1) >> 1;
		if (y[q[mid]] - y[q[mid - 1]] <= ti * (x[q[mid]] - x[q[mid - 1]])) L = mid;//判断两个点的优劣关系
		else R = mid - 1;
	}
	return q[L];
}
int main()
{
	scanf("%lld%lld", &n, &s);
	for (int i = 1; i <= n; ++i)
	{
		scanf("%lld%lld", &t, &c);
		sumt[i] = sumt[i - 1] + t;
		sumc[i] = sumc[i - 1] + c;
	}
	memset(f, 127/3, sizeof(f));
	f[0] = 0;
	q[1] = 0;
	l = 1;
	r = 1;
	for (int i = 1; i <= n; ++i)
	{
		h = find(sumt[i]);
		f[i] = f[h] + s * (sumc[n] - sumc[h]) + sumt[i] * (sumc[i] - sumc[h]);
		x[i] = sumc[i];
		y[i] = f[i] - s * sumc[i];
		while(l < r && (y[i] - y[q[r]]) * (x[q[r]] - x[q[r - 1]]) <= (y[q[r]] - y[q[r - 1]]) * (x[i] - x[q[r]]))//删除队列前面的点不影响,还是维护下凸包
			r--;
		q[++r] = i;
	}
	printf("%lld", f[n]);
	return 0;
}
posted @ 2021-01-25 21:01  ssllyf  阅读(82)  评论(1编辑  收藏  举报