DP优化

斜率优化DP总结

通常来说,斜率优化DP的状态转移方程式为:ios

\[dp[i]=min(dp[j]+val(i,j)) \]

其中,\(val(i,j)\)包含\(i\)\(j\)的乘积项。c++

对于此类问题,咱们通常会经过将该问题转化为维护凸包,经过规划进一步思考优化。优化

事实上,咱们更倾向于将方程变形,经过观察每一位优化。spa


例题:任务安排


在总结斜率优化DP以前,仍是要说一句,状态转移方程是才最关键的,状态转移方程想不出来,就完蛋code

这道题首先咱们考虑定义\(dp[i]\)表明前\(i\)台机器划分最小代价。队列

因为咱们不知道前面最优解划分了多少批任务,所以,咱们采用费用提早计算的思想进行解题。get

那么,咱们有:string

\[dp[i]=min(dp[j]+sumT[i]*(sumC[i]-sumC[j])+s*(sumC[n]-sumC[j])) \]

能够看出,在方程最后,咱们直接将\(j\)以后的全部的\(C[k]\)在此处进行累加。由于若是后面的状态转移到了该状态,那么后面的费用必定包含该次断点的\(s\)乘之后面的\(C[k]\)。不如咱们提早计算好了,方便转移,避免状态冗长。it


接下来,咱们进行斜率优化的变形:io

  • 不妨设最优解\(j\)处取得,那么有

    \[dp[i]=dp[j]+sumT[i]*(sumC[i]-sumC[j])+s*(sumC[n]-sumC[j]) \]

  • 变形得:

    \[dp[j]=(sumT[i]-s)*sumC[j]+dp[i]-sumT[i]*sumC[i]-s*sumC[n] \]

注意到,咱们将\(dp[j]\)看做\(y\),将\(sumC[j]\)当作\(x\),那么咱们要作的是最小化截距\(dp[i]-sumT[i]*sumC[i]-s*sumC[n]\)

事实上,上面那个变形并非惟一的。通常而言,将乘积项转化为\(kx\),把没有任何系数的项当作\(y\),其它项当作截距。

按照刚刚的表达式来说,咱们能够维护一个队列,队列中包括每一个“候选点”,用当前斜率\(sumT[i]-s\)进行查找,找到截距最小。接着,在更新完刚刚的值以后,咱们将新产生的值加入队列中,并维护凸包。

至于为何要维护凸包,咱们给出如下证实:

观察这张,中间那个点永远不多是最优势,所以,维护凸包,将这样的点干掉。

斜率优化细节仍是蛮多的,所以必定要注意几点:

  1. 更新队列时要保证队列中至少有两个元素;
  2. 当比较斜率大小时,通常使用乘法比较,此时不等号是否须要收积数符号的影响。

另外这道题的特殊性在于:每一次的斜率单调递增,于是采起删除队首元素便可。

#include<iostream>
#include<cstring>
#include<cstdio>
#include<cmath>
#define pii pair <int, int>
#define mp(x, y) make_pair(x, y)
#define FOR(i, a, b) for(register int i = a; i <= b; ++ i)
#define ROF(i, a, b) for(register int i = a; i <= b; -- i)
using namespace std;
const int N = 300000 + 5;
typedef long long LL;

int n, s, q[N];
LL T[N], C[N], sumT[N], sumC[N], dp[N];

int main()
{
	scanf("%d %d", &n, &s);
	memset(sumT, 0, sizeof(sumT));
	memset(sumC, 0, sizeof(sumC));
	FOR(i, 1, n)
	{
		scanf("%lld %lld", &T[i], &C[i]);
		sumT[i] = sumT[i - 1] + T[i], sumC[i] = sumC[i - 1] + C[i]; 
	}
	
	memset(dp, 0x3f, sizeof(dp));
	dp[0] = 0;
	int head = 1, tail = 1;
	q[tail] = 0;
	FOR(i, 1, n)
	{
		while(head < tail && (dp[q[head + 1]] - dp[q[head]]) < (sumT[i] + s) * (sumC[q[head + 1]] - sumC[q[head]])) ++ head;
		dp[i] = dp[q[head]] + sumT[i] * (sumC[i] - sumC[q[head]]) + s * (sumC[n] - sumC[q[head]]);
		while(head < tail && (dp[i] - dp[q[tail]]) * (sumC[q[tail]] - sumC[q[tail - 1]]) < (dp[q[tail]] - dp[q[tail - 1]]) * (sumC[i] - sumC[q[tail]])) -- tail;
		q[++ tail] = i;
	}
	printf("%lld\n", dp[n]);
	return 0;
}

若是这道题斜率不单调递增,那么咱们就二分最优值。

#include<iostream>
#include<cstring>
#include<cstdio>
#include<cmath>
#define CLR(a, x) memset(a, x, sizeof(a))
#define FOR(i, x, y) for(register int i = x; i <= y; ++ i)
#define ROF(i, x, y) for(register int i = x; i <= y; -- i)
#define pii pair <int, int>
#define mp(x, y) make_pair(x, y)
using namespace std;
const int N = 3e5 + 5;

typedef long long LL;

int n, s, q[N];
LL T[N], C[N], sumT[N], sumC[N], dp[N];
int main()
{
	scanf("%d %d", &n, &s);
	CLR(sumT, 0);
	CLR(sumC, 0);
	FOR(i, 1, n)
	{
		scanf("%lld %lld", &T[i], &C[i]);
		sumT[i] = sumT[i - 1] + T[i], sumC[i] = sumC[i - 1] + C[i];
	}
	
	CLR(dp, 0x3f);
	int head = 1, tail = 1, L, R, mid;
	dp[0] = 0;
	q[head] = 0;
	FOR(i, 1, n)
	{
		L = head, R = tail;
		
		while(L < R)
		{
			mid = L + ((R - L) >> 1);
			if(dp[q[mid + 1]] - dp[q[mid]]< (sumT[i] + s) * (sumC[q[mid + 1]] - sumC[q[mid]])) L = mid + 1;
			else R = mid;
		}
		dp[i] = dp[q[L]] + sumT[i] * (sumC[i] - sumC[q[L]]) + s * (sumC[n] - sumC[q[L]]);
		
		while(head < tail && (dp[i] - dp[q[tail]]) * (sumC[q[tail]] - sumC[q[tail - 1]]) < (dp[q[tail]] - dp[q[tail - 1]]) * (sumC[i] - sumC[q[tail]])) -- tail;
		q[++ tail] = i;
	}

	printf("%lld\n", dp[n]);
	return 0;
}

例题:运输小猫


考虑用每一个小猫时间减去到\(1\)号点的距离,sort,而后这道题跟上一道题相似,而且按照上一道题的作法,这道题斜率优化部分稍微容易。

#include<algorithm>
#include<iostream>
#include<cstring>
#include<cstdio>
#include<cmath>
#include<deque>
#define FOR(i, x, y) for(register int i = x; i <= y; ++ i)
#define ROF(i, x, y) for(register int i = x; i >= y; -- i)
#define CLR(a, b) memset(a, b, sizeof(a))
#define pii pair <int, int>
#define mp(x, y) make_pair(x, y)
using namespace std;
const int N = 1e5 + 5, P = 100 + 10;

typedef long long LL;

int n, m, p, D[N] = {}, q[N];
LL A[N], s[N], dp[P][N];
int main()
{
	CLR(s, 0);
	scanf("%d %d %d", &n, &m, &p);
	FOR(i, 2, n)
	{
		scanf("%d", &D[i]);
		D[i] += D[i - 1];	
	}
	FOR(i, 1, m)
	{
		LL H, T;
		scanf("%d %lld", &H, &T);
		A[i] = T - D[H];		
	}
	int head = 1, tail = 1;
	sort(A + 1, A + m + 1);
	FOR(i, 1, m) s[i] = s[i - 1] + A[i];
	CLR(dp, 0x3f);
	FOR(i, 0, p - 1) dp[i][0] = 0;
	FOR(i, 1, p)
	{
		head = tail = 1;
		q[tail] = 0;
		FOR(j, 1, m)
		{
			while(head < tail && (dp[i - 1][q[head + 1]] + s[q[head + 1]] - dp[i - 1][q[head]] - s[q[head]]) <= A[j] * (q[head + 1] - q[head])) ++ head;
			dp[i][j] = dp[i - 1][q[head]] + A[j] * (j - q[head]) - (s[j] - s[q[head]]);
			while(head < tail && (dp[i - 1][q[tail]] + s[q[tail]] - dp[i - 1][q[tail - 1]] - s[q[tail - 1]]) * (j - q[tail]) > (dp[i - 1][j] + s[j] - dp[i - 1][q[tail]] - s[q[tail]]) * (q[tail] - q[tail - 1])) -- tail;
			q[++ tail] = j;
		}
	}
	printf("%lld\n", dp[p][m]);
	return 0;
}

例题:K匿名序列


这道题也同样,就是维护的时候必定要当心便可。

#include<iostream>
#include<cstring>
#include<cstdio>
#include<cmath>
#define FOR(i, x, y) for(register int i = x; i <= y; ++ i)
#define ROF(i, x, y) for(register int i = x; i >= y; -- i)
#define CLR(x, y) memset(x, y, sizeof(x))
using namespace std;
const int N = 500000 + 7;

typedef long long LL;

int n, k, a[N], q[N];
LL b[N], s[N], dp[N];

LL d_x(int x, int y)
{
	return a[x + 1] - a[y + 1];
}

LL d_y(int x, int y)
{
	return (dp[x] - dp[y]) - (s[x] - s[y]) + (b[x] - b[y]);
}

void prework()
{
	CLR(a, 0), CLR(b, 0);
	CLR(s, 0), CLR(q, 0);
	a[n + 1] = 1 << 30;
	return;
}
int main()
{
	int T;
	scanf("%d", &T);
	while(T --)
	{
		scanf("%d %d", &n, &k);
		prework();
		FOR(i, 1, n)
		{
			scanf("%d", &a[i]);
			s[i] = s[i - 1] + a[i];
		}
		FOR(i, 1, n) b[i] = 1ll * a[i + 1] * i;
		int head = 1, tail = 1;
		CLR(dp, 0x3f);
		q[tail] = 0;
		dp[0] = 0;
		FOR(i, k, n)
		{
			while(head < tail && d_y(q[head + 1], q[head]) <= i * d_x(q[head + 1], q[head])) ++ head;
			dp[i] = dp[q[head]] + s[i] - s[q[head]] - a[q[head] + 1] * (i - q[head]);
			if(i + 1 >= k * 2)
			{
				while(head < tail && d_y(q[tail], q[tail - 1]) * d_x(i - k + 1, q[tail]) >= d_y(i - k + 1, q[tail]) * d_x(q[tail], q[tail - 1])) -- tail;
				q[++ tail] = i - k + 1;
			}
		}
		printf("%lld\n", dp[n]);
	}
	return 0;
}
相关文章
相关标签/搜索