斜率优化 DP :Luogu P2365 P5785「SDOI2012」任务安排 & 弱化版

题面

动态规划

为了方便表示,题面中的费用系数改用 \(v_i\)

确定 \(f_i\) 表示到第 \(i\) 个位置的最优解

每次转移就是钱 \(i\) 个加上一段 \([j+1,i]\) 的花费

很显然,可以用前缀和优化

但因为有 \(s\) 的存在,使得直接加上多开一维需记录分组数,却会增加巨大的开销,方程给出,感受一下 \(n^3\) 算法:

\[f_{i,k} = \min_{1\leq j\leq i-1}f_{j,k-1}+\sum_{l=j}^i v_l \times (\sum_{l=1}^i t_l+s \times k) \]

这个方程出现了大量无用状态,因为最终只需要求出一个位置上的最优解

那我们反过来想,每一次的分组,都会使得时间拖延 \(s\) ,那么事先加上就行了

即在分为 \(k\) 组时,前面已经加上了 \(k\)\(s\) 所带来的影响值

也就是 \(s \times \sum_{l=j+1}^n v_l\)

给出最终的时间复杂度为 \(\mathcal{O}(n^2)\) 转移方程:

\[f_i = \min_{1\leq j\leq i-1} f_j + \sum_{l=j}^i v_l \times \sum_{l=1}^i t_l + s \times \sum_{l=j+1}^n v_l \]

将所有的求和用前缀和维护

然后就能愉快的敲代码了

代码给出:

#include<bits/stdc++.h>
using namespace std;
const int maxn = (int)5e3+7;
int n,s,f[maxn],t[maxn],v[maxn];
int main() {
	scanf("%d%d",&n,&s);
	for(int i=1;i<=n;++i) {
		scanf("%d",t+i);t[i] += t[i-1];
		scanf("%d",v+i);v[i] += v[i-1];
	}
	memset(f,0x3f,sizeof f);
	f[0] = 0;
	for(int i=1;i<=n;++i)
		for(int j=0;j<i;++j) {
			f[i] = min(f[i],f[j]+t[i]*(v[i]-v[j])+s*(v[n]-v[j]));
		}
	printf("%d",f[n]);
	return 0;
}

Upd 2020.10.30

以上是 P2365 的 \(\mathcal{O}(n^2)\) 做法,接下来介绍该题 \(\mathcal{O}(n)\) 的斜率优化 DP

注: 下文中 \(sumt_i \gets \sum_{j=1}^i t_j\) , \(sumv_i \gets \sum_{j=1}^i v_j\)

把原方程中的 \(\min\) 去掉得

\[f_i = f_j + (sumv_i-sumv_j) \times sumt_i + s \times (sumv_n-sumv_j) \]

展开,移项得

\[\underline{f_j-s\times sumv_j}_{\ y} = \underline{(sumt_i+s)}_{\ k}\times \underline{sumv_j}_{\ x} + \underline{f_i-sumv_i\times sumt_i-s\times sumv_n}_{\ b} \]

tips: 斜率优化拆项时, \(x,y\) 必须与 \(j\) 相关, \(k\) 必须与 \(i\) 相关

问题转化为斜率 \(k=sumt_i+s\) 时,取一个点使得直线在 \(y\) 轴上的截距最大,其中点就为 \((sumv_j,f_j-s\times sumv_j)\)

我们用线性规划的思想,将斜率为 \(k=sumt_i+s\) 的直线从下往上移动,碰到的第一个点即能得到最小截距

此题中, \(k\) 单调上升, \(x\) 单调上升,坐标系上的点长这样

上图中,用线性规划的取点方式,红点是无论如何都取不到的,我们要维护蓝点所形成的几何图形,称之为凸包

我们用一个单调队列来维护凸包

因为 \(x\) 是单增的,所以每次加入新点都在后方,我们考虑下图的情况

其中红点是新加入的点, tail 表示队尾, tail-1 表示队列的队尾的前一个,此时加入新点后凸包会被破坏,于是我们就弹出队尾的这个点

为了每次都能 \(\mathcal{O}(1)\) 取到最优点,我们用队头来维护

如上图所示,当队头和队头后一个点组成直线的斜率小于 \(k=sumt_i+s\) 时,又因为 \(k\) 单增,此时队头将不会被取到,弹出即可

tips: 1. \(\frac{y_1-y_2}{x_1-x_2} \leq \frac{y_3-y_4}{x_3-x_4}\) 可转化为 \((y_1-y_2)\times(x_3-x_4)\leq(y_3-y_4)\times(x_1-x_2)\) 避免精度问题
2. 不同题目中的斜率优化时的斜率可能是单调下降的,应另画图分析

代码实现如下

#include<bits/stdc++.h>
#define forn(i,s,t) for(int i=(int)s;i<=(int)t;++i)
using namespace std;
const int R = (int)3e5+7;
int N,Q[R];
long long s,T[R],V[R],f[R];
int main() {
	scanf("%d%lld",&N,&s);
	forn(i,1,N) scanf("%lld%lld",&T[i],&V[i]),
				T[i] += T[i-1],V[i] += V[i-1];       // 前缀和
	int h=0,t=0;
	forn(i,1,N) {
		while(h<t&&f[Q[h+1]]-f[Q[h]]<=(T[i]+s)*(V[Q[h+1]]-V[Q[h]]))             // 弹出队头
			++h;
		f[i] = f[Q[h]] + T[i]*(V[i]-V[Q[h]]) + s*(V[N]-V[Q[h]]);                // 转移
		while(h<t&&(f[i]-f[Q[t]])*(V[Q[t]]-V[Q[t-1]])<=(f[Q[t]]-f[Q[t-1]])*(V[i]-V[Q[t]]))  // 弹出队尾
			--t;
		Q[++t] = i;
	}
	printf("%lld\n",f[N]);
	return 0;
}

Upd 2020.10.31

以上是朴素的斜率优化的实现,我们来看下一题【SDOI2012】的加强版

题面

乍一看好像没什么区别,但注意,该题的 \(t_i\) 可以为负数,也就是说上文中的 \(k=sumt_i+s\) 不再单调递增了,但 \(x\) 仍单调递增

也就是说我们无法和上题一样直接维护队头为答案了,但队尾还能一样维护凸包,我们观察下下凸包(向下凸的凸包)的性质

仔细观察上图,可以发现 \(k_f<k_g<k_h\) ,即相邻两个点的斜率是单调递增的

有了这个性质,我们就可以用二分查找的方式找出第一条斜率 \(k\geq sumt_i+s\) 的直线的后一个点,并进行转移

时间复杂度 \(\mathcal{O}(n\log n)\)

具体实现见代码

#include<bits/stdc++.h>
#define forn(i,s,t) for(int i=(int)s;i<=(int)t;++i)
using namespace std;
const int R = (int)3e5+7;
int N,Q[R];
long long s,T[R],V[R],f[R];
int main() {
	scanf("%d%lld",&N,&s);
	forn(i,1,N) scanf("%lld%lld",&T[i],&V[i]),                 // 前缀和
				T[i] += T[i-1],V[i] += V[i-1];
	int h=0,t=0,l,r,mid,res;
	forn(i,1,N) {
		l = h,r = t,res = Q[r];
		while(l<=r) {                                      // 二分找点
			mid = l+r >> 1;
			if((f[Q[mid+1]]-f[Q[mid]])>(T[i]+s)*(V[Q[mid+1]]-V[Q[mid]])) r = mid-1,res = Q[mid];
			else l = mid+1;
		}
		f[i] = f[res] + T[i]*(V[i]-V[res]) + s*(V[N]-V[res]);      // 转移
		while(h<t&&(f[i]-f[Q[t]])*(V[Q[t]]-V[Q[t-1]])<=(f[Q[t]]-f[Q[t-1]])*(V[i]-V[Q[t]]))
			--t;                                       // 弹出队尾维护凸包
		Q[++t] = i;                                        // 加入新点
	}
	printf("%lld\n",f[N]);
	return 0;
}
posted @ 2020-03-20 17:02  AxDea  阅读(201)  评论(0编辑  收藏  举报