cdq實現斜率優化程式碼(帶註釋)
阿新 • • 發佈:2022-02-13
因為博主蒟蒻的原因, 在第一次寫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值的處理與輸出 }