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;
}
相關文章
相關標籤/搜索