1. 程式人生 > 其它 >cdq實現斜率優化程式碼(帶註釋)

cdq實現斜率優化程式碼(帶註釋)

因為博主蒟蒻的原因, 在第一次寫cdq實現斜率優化用了快一天的時間才把程式碼改對, 所以寫一篇部落格記錄一下寫法以及演算法的基本實現思路。

#include <cstdio>
#include <algorithm>
using namespace std;

#define INF 0x3f3f3f3f3f3f3f3f
#define MAXN 100000

struct cdq_node {//斜率優化的基本形式:y = kx + b
	int loc;
	long long x, k;
	long long constant;//即是上式中的b
	long long y;
}s_cdq[MAXN + 5], tem[MAXN + 5];
long long dp[MAXN + 5];//轉移的dp值
int q[MAXN + 5];

bool cmp (cdq_node a, cdq_node b) {//最開始按照k排序, 以達到O(nlogn)時間
	return a.k < b.k;
}
double slope (cdq_node a, cdq_node b) {//斜率, 由於轉為乘法會出現因正負變不等號方向的問題, 而c++的高精還是挺準的, 所以還是使用除法
	return 1.0 * (a.y - b.y) / (a.x - b.x);
}
long long level (cdq_node a, cdq_node b) {//算出a對b的貢獻與a有關的部分
	return a.y - a.x * b.k;
}
void transfer (int l, int mid, int r) {//將左區間的狀態轉移到右區間中
	int h = 1, t = 0;
	//以左區間構建凸包
	for (int e = l; e <= mid; e ++) {
		if (e != l && s_cdq[e].x == tem[e - 1].x) {
			continue;
		}
		while (t > h && slope (s_cdq[q[t]], s_cdq[e]) <= slope (s_cdq[q[t - 1]], s_cdq[q[t]])) {
			t --;
		}
		q[++ t] = e;
	}
	//向右區間進行轉移
	for (int e = mid + 1; e <= r; e ++) {
		while (h < t && level (s_cdq[q[h]], s_cdq[e]) >= level (s_cdq[q[h + 1]], s_cdq[e])) {
			h ++;
		}
		if (level (s_cdq[q[h]], s_cdq[e]) + s_cdq[e].constant < dp[s_cdq[e].loc]) {
			dp[s_cdq[e].loc] = level (s_cdq[q[h]], s_cdq[e]) + s_cdq[e].constant;
		}
	}
}
//將按照k排序的兩個區間裂成位置在mid左和右的兩部分, 因為要保證右區間按照k遞增, 而左區間會先進行遞迴, 返回時歸併排序就可以使其順序正確, 所以用分裂的方法, 使未使用超過O(1)的資料結構時總時間為O(nlogn)
void split (int l, int r, int mid) {
	int i = l, j = mid + 1;
	
	for (int e = l; e <= r; e ++) {
		if (s_cdq[e].loc <= mid) {
			tem[i ++] = s_cdq[e];
		}
		else {
			tem[j ++] = s_cdq[e];
		}
	}
	for (int e = l; e <= r; e ++) {
		s_cdq[e] = tem[e];
	}
}
//cdq中的歸併, 無需過多解釋
void merge (int l, int mid, int r) {
	int i = l, j = mid + 1;
	int k = l;
	
	while (i <= mid && j <= r) {
		if (s_cdq[i].x < s_cdq[j].x || (s_cdq[i].x == s_cdq[j].x && s_cdq[i].y < s_cdq[j].y)) {
			tem[k ++] = s_cdq[i ++];
		}
		else {
			tem[k ++] = s_cdq[j ++];
		}
	}
	while (i <= mid) {
		tem[k ++] = s_cdq[i ++];
	}
	while (j <= r) {
		tem[k ++] = s_cdq[j ++];
	}
	for (int e = l; e <= r; e ++) {
		s_cdq[e] = tem[e];
	}
}
//整體拼接起來
void cdq (int l, int r) {
	if (l == r) {
		...//這裡需要計算x, y 中與dp有關的項的值,此時這兩項並未參與到轉移, 而dp值由於不會從右往左轉移, 所以此時也是求出準確答案的了
		return ;
	}
	
	int mid = (l + r) >> 1;
	//按照順序執行操作
	split (l, r, mid);//先歸併左邊的區間, 因為右邊的區間並不能向左轉移, 所以先向下遞迴, 進行區間內的轉移
	cdq (l, mid);//由於左區間會影響右區間, 所以轉移左區間到右區間後再進行右區間內的轉移
	transfer (l, mid, r);
	cdq (mid + 1, r);
	merge (l, mid, r);
}

int main () {
	...//輸入等一系列初始化。
	dp[...] = ...;//將初始位置附上初值
	for (int i = /*1或0, 視情況而定*/; i <= n; i ++) {
		//將與dp無關的量先求出
		s_cdq[i].loc = i;
		s_cdq[i].constant = ...;
		s_cdq[i].k = ...;
		s_cdq[i].x = ...;
		s_cdq[i].y = ...;
	}
	sort (s_cdq + 1, s_cdq + 1 + n, cmp);//按照k排序後在進行cdq
	cdq (1, n);
	...//之後對dp值的處理與輸出
}