學習筆記 wqs二分 dp凸優化

2022-03-31 17:17:57 字數 3754 閱讀 8144

## 從乙個經典問題談起:

有乙個長度為 \(n\) 的序列 \(a\),要求找出恰好 \(k\) 個不相交的連續子串行,使得這 \(k\) 個序列的和最大

\(1 \leq k \leq n \leq 10^5, -10^9 \leq a_i \leq 10^9\)

先假裝都會 \(1 \leq k \leq n \leq 1000\) 的 \(dp\) 做法以及 \(k = 1\) 的子問題

實際上這個問題還可以是個費用流模型:

對於序列中每乙個點 \(i\) ,拆成兩個點 \(i\) 和 \(i'\) ,連一條 \(i \rightarrow i'\) 流量為 \(1\) 費用為 \(a_i\) 的邊

對於每乙個 \(i\) ,連一條 \(s \rightarrow i\) 流量為 \(1\) 費用為 \(0\) 的邊

對於每乙個 \(i'\) ,連一條 \(i' \rightarrow t\) 流量為 \(1\) 費用為 \(0\) 的邊

對於相鄰的兩個點 \(i\) 和 \(i + 1\) ,連一條 \(i'\) 到 \(i+1\) 流量為 \(1\) 費用為 \(0\) 的邊

顯然每次沿著最大費用路徑單路增廣一次的話就是選擇了原問題的乙個最大連續子串行

實際上這樣增廣 \(k\) 次後的結果就是答案,因為有反向邊的存在所以選出來的區間不會相交

這個做法的複雜度其實並沒有直接 \(dp\) 優,但是可以基於這個模型進行很多優化

線段樹優化:\(\text\)

把模型放到原問題上,每一次單路增廣相當於是求全域性的最大連續子段和然後將其取反

直接用線段樹維護這兩個操作,複雜度優化到 \(o(klogn)\)

資料結構的優化在這個問題上還算適用,但是對於問題的模型有一定的侷限性

顯然上述做法不是本文的重點,不妨繼續考慮這個費用流做法

觀察發現由於每次單路增廣的是最長路,增廣後的網路是之前網路的殘餘網路,所以每一次增廣得到的費用都會比上一次得到的要少。

也就是說,如果設 \(f(x)\) 為增廣 \(x\) 以後的總流量,\(f(x)\) 的函式影象是乙個上凸包

實際上 \(f(x)\) 等價於選取了 \(x\) 個不相交的連續子串行的最大和,也就是原問題。

考慮除了用資料結構進行繁瑣的維護以外,我們並沒有什麼辦法高效的直接求出 \(f(x)\) 的每一項

但設 \(\max(f(x))\) 的值可以通過 \(o(n)\) 就可以求出,在這個問題裡就是把所有 \(> 0\) 的數加起來

也就是說,我們可以簡單的求出這個函式的極點的值,這啟發我們可以通過對函式進行魔改,使得極點在 \(k\) 上

由於函式是上凸的,不妨設 \(f'(x) = f(x) + px\) ,顯然當 \(p\) 的值增加時,極點的位置會左移

那麼問題就轉化為找到乙個合適的斜率 \(p\) 使得 \(f'(x) = f(x) + px\) 的最大值在 \(x =k\) 時取到

也就是拿一條斜率為 \(p\) 的直線去且這個凸包使得切點恰好在 \(x = k\) 上,由於凸包的性質切線的斜率是單調的

那麼不妨二分斜率 \(p\),對於 \(f'(x) = f(x) + px\) 的取值加以驗證,而把這個函式放回到原問題上,就是每選乙個區間需要 \(p\) 的額外代價

於是就可以 \(dp\) 出在數量不限,每選乙個區間要 \(p\) 的額外代價的情況下,能獲得的最優總代價是什麼,最優解選了多少個區間

這對應的是 \(f'(x)\) 的最大值以及取到最大值的 \(x\) ,根據這個可以判斷出接下來斜率該增大還是減小

如果某一時刻得到最大值取到的位置為 \(x = k\) ,那麼原問題的答案就是 \(f'(x) - px\) ,轉化回去即可

此外還需要考慮乙個細節,這個所謂的上凸包其有些點的取值並非在凸包的頂點上而是在凸包的邊上,這樣的話直線只能切到這條邊而不能切到點了

但是考慮此時的頂點是可以切到的,所以只需要在 \(dp\) 的時候記錄最優解取到的最左/最右位置即可,最後同樣能得到正確的斜率 \(p\) ,此時這條邊上的取值是相同的

至此就用乙個二分和乙個不帶限制的 \(dp\) 以 \(o(nlogk)\) 的限制解決了此題,事實上但凡答案的形態是凸的題目都可以嘗試用這種方法解決,相較於資料結構有很大的優勢

## 乙個例題(為了貼**):

在樹上選取 \(k + 1\) 條點不相交的鏈,使得選取的邊權和最大

類似的,問題可以轉化為每次選樹的直徑,然後給樹的直徑取反,這樣的話函式的上凸性就顯然了

當然這裡也可以用資料結構來維護這個模型,(lct 優化費用流),不過實在太毒瘤了想必也沒什麼人會寫吧

相反,wqs二分/dp凸優化(其實是乙個東西)的做法在這裡就十分清真

類似的,二分斜率以後等價於每選一條鏈要花費 \(p\) 的額外代價,然後進行簡單的樹 \(dp\) 就可以了

\(f[u][0]\) 表示 \(u\) 子樹內 \(u\) 不是任意一條所選鏈上的點,能獲得的最大收益

\(f[u][1]\) 表示 \(u\) 子樹內 \(u\) 是一條所選鏈的端點,能獲得的最大收益

\(f[u][2]\) 表示 \(u\) 子樹內 \(u\) 是一條所選鏈的 \(lca\),能獲得的最大收益 (轉移請自行推導)

總複雜度 \(o(nlogk)\)

/*program by mangoyang*/

#include#define inf ((ll)(0x7f7f7f7f))

#define max(a, b) ((a) > (b) ? (a) : (b))

#define min(a, b) ((a) < (b) ? (a) : (b))

typedef long long ll;

using namespace std;

template inline void read(t &x)

#define int ll

const int n = 700005;

int slope;

int a[n], b[n], nxt[n], head[n], cnt, n, k;

struct node; }

node operator + (const node &a) const; }

bool operator > (const node &a) const

}dp[n][3];

inline void addedge(int x, int y, int z)

inline void chkmax(node &x, node y)

inline void solve(int u, int fa);

for(int p = head[u]; p; p = nxt[p]) if(a[p] != fa)

chkmax(dp[u][0], max(dp[u][2], dp[u][1] + add));

}inline bool check(int mid)

return dp[1][0].cnt > k;

}signed main()

int l = (ll) -1e12, r = (ll) 1e12, ls;

while(l <= r)

check(ls);

printf("%lld\n", dp[1][0].ans + k * ls);

return 0;

}

apio2018 講課

zx2003大爺的部落格

學習筆記 凸優化 WQS二分 帶權二分

從乙個題帶入 八省聯考2018 林克卡特樹lct wqs二分 比較詳細的 題解 p4383 八省聯考2018 林克卡特樹lct 簡單總結和補充 凸函式,限制 二分斜率,找切點橫縱座標,判斷k的位置 找切點座標 集體 mid x 證明還是凸函式 f x 2 f x 1 f x 1 f x 仍然成立 每...

WQS二分 學習筆記

我的理解 不一定很對 大概就是某個東西越多總貢獻越大,要求剛好取n個時的最優解。可以把 dp 狀態裡記的取的個數這一維去掉,而設乙個 co st,取 k 個物品,總貢獻要多減去cost k,然後 dp cos t 越大,物品數取越少。二分 co st使得 dp 得到的答案剛好取了 n 個物品。設 g...

WQS二分學習筆記

wqs 二分聽起來是個很難的演算法,其實學起來也並不是那麼難。在某些題目中,會對於某個取得越多越優的物品,限定你最多選擇 k 個,問你能得到的最優答案。例如這道題目 cf739e gosha is hunting。這些題目一般都可以通過列舉選擇的物品個數並 o n dp 來做到 o nk 但如果隨著...