動態規劃
區間動態規劃、狀態壓縮DP
動態規劃
-
最佳子結構
- 問題本身的最佳解可以從子問題的最佳解求得
-
重複子問題
- 相同的子問題重複出現
動態規劃
ex. 背包問題的最佳子結構
dp[W]=max_{i=1}^k\{dp[W-w_i]+v_i\}
dp[W]=maxi=1k{dp[W−wi]+vi}
背包重量
最高價值
假設有三個物品(k=3), 重量與價值分別為
w=\{1,2,5\}\ \
v=\{1,4,3\}
w={1,2,5} v={1,4,3}
W=6
W=6
4
4
1
1
1
8
9
dp[6]=max\{9+1, 8+4, 1+3\}=12
dp[6]=max{9+1,8+4,1+3}=12
dp[W]
dp[W]
5
4
5
5
0
動態規劃
ex. 背包問題的重複子問題
背包重量
最高價值
W=5
W=5
4
4
1
8
dp[W]
dp[W]
5
4
3
3
背包重量
最高價值
w=\{1,2,5\}\ \
v=\{1,4,3\}
w={1,2,5} v={1,4,3}
W=6
W=6
4
4
1
1
1
8
9
dp[W]
dp[W]
5
4
5
5
0
0
0
0
區間動態規劃
-
顧名思義,dp陣列是儲存某個區間的最佳解
-
最佳子結構、重複子問題
L
R
L
R
i
i+1
dp[L][R] = max_{i=L}^{R-1}\{dp[L][i] + dp[i+1][R]+w(L,i,R)\}
dp[L][R]=maxi=LR−1{dp[L][i]+dp[i+1][R]+w(L,i,R)}
區間動態規劃 zerojudge - d652
題目:
給長度為n(<50)的正整數數列,每次操作可以合併相鄰的3個數,合併後中間的數被刪除,付出代價為三數相乘。
例如:相鄰三數為A, B, C則合併後剩下A, C操作代價為三數相乘AxBxC。
求合併到只剩2個數,操作過程代價總和最少為何
範例輸入:
1 2 3 4 5
範例輸出:
38
1 2 3 4 5
0
1 3 4 5
6
1 4 5
18
1 5
38
區間動態規劃 zerojudge - d652
dp[L][R] = max_{i=L}^{R-1}\{dp[L][i] + dp[i+1][R]+w(L,i,R)\}
dp[L][R]=maxi=LR−1{dp[L][i]+dp[i+1][R]+w(L,i,R)}
-
觀察操作有什麼特性
-
定義dp陣列每個維度index的意義
-
根據定義的意義嘗試找出轉移方法
-
找不出轉移方法->從第2步開始
區間動態規劃 zerojudge - d652
-
某一區間合併到最後會剩下最左邊和最右邊兩個數
-
dp[L][R]代表將區間[L,R]合併到剩下區間端點兩數的最小代價
-
dp[L][R] = max_{i=L}^{R}\{dp[L][i] + dp[i][R]+w[L]\times
w[i]\times w[R]\}
dp[L][R]=maxi=LR{dp[L][i]+dp[i][R]+w[L]×w[i]×w[R]}
L
R
L
R
i
i
i
L
i
R
區間動態規劃 zerojudge - d652
-
觀察
-
合併到最後會剩下最左邊和最右邊兩個數
- 假如合併的最後一步剩下W[0], W[i], W[n-1],代表W[0]和W[i]之間都合併完、W[i]和W[n-1]之間也合併完
- 你不知道哪個i會使代價最低
-
-
dp[L][R]代表將區間[L,R]合併到剩下區間端點兩數的最小代價
-
根據定義的dp陣列和觀察
dp[L][R] = max_{i=L}^{R}\{dp[L][i] + dp[i][R]+w[L]\times
w[i]\times w[R]\}
dp[L][R]=maxi=LR{dp[L][i]+dp[i][R]+w[L]×w[i]×w[R]}
區間動態規劃 zerojudge - d652
#include <...>
const int N = ???;
int dp[N][N];
int DP(int l, int r) {
if(dp[l][r] != -1) // 已經 DP過
return dp[l][r];
if(邊界條件) {
dp[l][r] = 0;
return dp[l][r];
}
dp[l][r] = 1e9 + 1;
for(int i = l + 1; i < r; i++)
dp[l][r] = min(dp[l][r], DP(l, i) + DP(i, r) + cost(l, i, r));
return dp[l][r];
}
int main () {
// 輸入
memset(dp, -1, sizeof(dp));
printf("%d\n", DP(0, n - 1));
}區間動態規劃 zerojudge - d652
#include <...>
const int N = ???;
int dp[N][N];
int main () {
// 輸入
memset(dp, 0, sizeof(dp));
for(int len = 3; len <= n; len++)
for(int l = 0; l + len - 1 < n; l++) {
int r = l + len - 1;
dp[l][r] = 1e9;
for(int i = l+1; i < r; i++)
dp[l][r] = min(dp[l][r], dp[l][i] + dp[i][r] + cost(l, i, r));
}
printf("%d\n", dp[0][n - 1]);
}區間動態規劃 zerojudge - d652
時間複雜度:
- 每個區間可以有n種子區間的組合,因此轉移一次要花
- 總共有 個區間(n個左界和n個右界)
- 複雜度為
n^2
n2
O(n)
O(n)
O(n^3)
O(n3)
更多區間動態規劃
uva10304:
題目大意:
權重由小到大給二元搜尋樹上每個葉子被訪問的次數e,每一次訪問的代價是葉子的深度d,所以一個葉子的總代價在二元樹中的代價為d*e,請找出一顆二元搜尋樹,使得訪問每個葉節點的總代價最低
區間動態規劃 四邊形不等式優化 HDU 3506
更多動態規劃
滾動數組DP UVA 12484
四邊形不等式優化
-
a < b ≤ c <d, f(a,c) + f(b,d) ≤ f(a,d) + f(b,c)
-
假設k<d,如果有f(L,i)+f(i,R)≥f(L,d)+f(d,R),其中 d 是轉移 f(L, R) 最好的斷點(也稱為K(L, R)),則小於 d 的 i 當然結果都會比用 d 轉來的差。
-
a < b ≤ c <d, f(a,d) + f(b,c) ≧ f(a,c) + f(b,d)
-
假設i<d,如果有f(L,i)+f(i,R)≥f(L,d)+f(d,R),其中 d 是轉移 f(L, R) 最好的斷點(也稱為K(L, R)),則小於 d 的 i 當然結果都會比用 d 轉來的差。
- 由式1可以得到 f(L+1,i)+f(L,d)≥f(L+1,d)+f(L,i);(L<L+1<i<d)
- ⇒f(L+1,i)+f(i,R)+f(L,d)+f(d,R)≥f(L+1,d)+f(d,R)+f(L,i)+(i,R), 兩邊同加 f(i,R)+f(d,R)
- ⇒(f(L+1,i)+f(i,R)+f(L,d)+f(d,R))−(f(L+1,d)+f(d,R)+f(L,i)+f(i,R))≥0
- ⇒(f(L+1,i)+f(i,R))−(f(L+1,d)+f(d,R))≥(f(L,i)+f(i,R))−(f(L,d)+f(d,R))
- 因為式2, (f(L,i)+f(i,R))−(f(L,d)+f(d,R))≥0
- ⇒(f(L+1,i)+f(i,R))−(f(L+1,d)+f(d,R))≥0
- ⇒f(L+1,i)+f(i,R)≥f(L+1,d)+f(d,R) 重要結論
I(L,R-1) \leq d
I(L,R−1)≤d
d\leq K(L+1,R)
d≤K(L+1,R)
f(L+1,i)+f(i,R) \geq f(L+1,d)+f(d,R)
f(L+1,i)+f(i,R)≥f(L+1,d)+f(d,R)
i < d
i<d
因為
所以
I(L,R-1) \leq d \leq K(L+1,R)
I(L,R−1)≤d≤K(L+1,R)
結論
狀態壓縮DP
-
最佳子結構、重複子問題
-
表達狀態的變數過多,無法只用兩三個維度的陣列表達
-
每個變數只有0和1兩個值
狀態壓縮DP zerojudge-d879
題目:
給2n(n<=8)個人,每個人位於平面上的一個點,希望將這2n個人組隊,每隊兩個人,組成一個隊伍的成本是兩個人之間的距離,求把所有人分組最小的成本
方法1:
用DFS直接爆搜,每層遞迴先選擇一個待分組的人當固定的成員,再依序枚舉沒有被分派組別的人給這個固定成員選組成一隊,繼續遞迴。
原理:
每個人最終會屬於某一組,所以每層遞迴可以固定一個人
狀態壓縮DP zerojudge-d879
狀態壓縮DP zerojudge-d879
...
發現重複子問題5,6,7,8
1
2
3
4
1
2
3
4
5
6
7
8
5
6
7
8
狀態壓縮DP zerojudge-d879
-
用1表示還沒有被分組,0表示已經有組
-
上一張投影片的相同子狀態可以表示成00001111
(15)_{10} = (00001111)_2
(15)10=(00001111)2
狀態壓縮DP zerojudge-d879
#include <...>
...
void DP(int state) {
int fix;
for(fix = 0; fix < n; fix++)
if(state & (1 << fix)) // 如果第fix個bit不是0
break;
state &= ~(1 << fix); // 把第fix個bit變0
for(int i = 0; i < n; i++)
if(state & (1 << i)) {
int new_state = state & ~(1 << i); // 把第i個bit變0
if(new_state 還沒dp過) // 避免重複子問題
DP(new_state);
// state的最佳解可能是new_state的最佳解加上fix和i同組的cost
dp[state] = min(dp[state], dp[new_state] + cost(fix, i));
}
}
int main() {
...
// 輸入
DP((1 << n) - 1); // (1 << n) - 1 二進位會是n個1
cout << dp[(1 << n) - 1] << endl;
}更多狀態壓縮
更多狀態壓縮
-
輪廓線動態規劃
動態規劃
By w86763777
動態規劃
- 699