Skip to content

Commit e2f08ee

Browse files
committed
✨ feat(dp优化): divide
1 parent 81f91f4 commit e2f08ee

File tree

115 files changed

+4892
-650
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

115 files changed

+4892
-650
lines changed

11_动态规划/acwingdp专项练习/环形与后效性处理/213. 打家劫舍 II-环形分类讨论.py

Lines changed: 16 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -25,26 +25,19 @@ def dfs(index: int, hasPre: bool, root: bool) -> int:
2525

2626
def rob2(self, nums: List[int]) -> int:
2727
"""dp 考虑第一个选还是不选"""
28-
n = len(nums)
29-
if n == 1:
30-
return nums[0]
31-
32-
dp = [[[0, 0] for _ in range(2)] for _ in range(n)] # (index,pre,root) [不选 选]
33-
dp[0][1][1] = nums[0]
34-
for i in range(1, n):
35-
for pre in range(2):
36-
for cur in range(2):
37-
if pre == cur == 1:
38-
continue
39-
for root in range(2):
40-
dp[i][cur][root] = max(
41-
dp[i][cur][root], dp[i - 1][pre][root] + (cur and nums[i])
42-
)
43-
44-
res = -INF
45-
for pre in range(2):
46-
for root in range(2):
47-
if pre == root == 1:
48-
continue
49-
res = max(res, dp[n - 1][pre][root])
50-
return res
28+
if not nums:
29+
return 0
30+
31+
def cal0() -> int: # 不偷第一个(i的范围是[1, n-1])
32+
dp0, dp1 = 0, 0
33+
for i in range(1, len(nums)):
34+
dp0, dp1 = max(dp0, dp1), max(dp0 + nums[i], dp1)
35+
return max(dp0, dp1)
36+
37+
def cal1() -> int: # 偷第一个(i的范围是[2, n-2])
38+
dp0, dp1 = 0, 0
39+
for i in range(2, len(nums) - 1):
40+
dp0, dp1 = max(dp0, dp1), max(dp0 + nums[i], dp1)
41+
return max(dp0, dp1)
42+
43+
return max(cal0(), cal1() + nums[0])

11_动态规划/dp优化/OfflineOnline.go

Lines changed: 0 additions & 3 deletions
This file was deleted.
Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
// 単調最小値DP (aka. 分割統治DP) 优化 offlineDp
2+
// https://ei1333.github.io/library/dp/divide-and-conquer-optimization.hpp
3+
// !用于高速化 dp[k][j]=min(dp[k-1][j]+f(i,j)) (0<=i<j) 一般是dp[remain][index]
4+
// 如果f满足决策单调性 那么对转移的每一行,可以采用 monotoneminima 寻找最值点
5+
// O(kn^2)优化到O(knlogn)
6+
7+
package main
8+
9+
import (
10+
"fmt"
11+
"sort"
12+
)
13+
14+
const INF int = 1e18
15+
16+
// !dist(i,j): 左闭右开区间[i,j)的代价(0<=i<j<=n)
17+
func divideAndConquerOptimization(k, n int, dist func(i, j int) int) [][]int {
18+
dp := make([][]int, k+1)
19+
for i := range dp {
20+
dp[i] = make([]int, n+1)
21+
for j := range dp[i] {
22+
dp[i][j] = INF // !INF if get min
23+
}
24+
}
25+
dp[0][0] = 0
26+
27+
for i := 1; i <= k; i++ {
28+
getCost := func(y, x int) int {
29+
if x >= y {
30+
return INF
31+
}
32+
return dp[i-1][x] + dist(x, y)
33+
}
34+
res := monotoneminima(n+1, n+1, getCost)
35+
for j := 0; j <= n; j++ {
36+
dp[i][j] = res[j][1]
37+
}
38+
}
39+
40+
return dp
41+
}
42+
43+
func monotoneminima(H, W int, dist func(i, j int) int) [][2]int {
44+
dp := make([][2]int, H) // dp[i] 表示第i行取到`最小值`的(索引,值)
45+
46+
var dfs func(top, bottom, left, right int)
47+
dfs = func(top, bottom, left, right int) {
48+
if top > bottom {
49+
return
50+
}
51+
52+
mid := (top + bottom) / 2
53+
index := -1
54+
res := 0
55+
for i := left; i <= right; i++ {
56+
tmp := dist(mid, i)
57+
if index == -1 || tmp < res { // !less if get min
58+
index = i
59+
res = tmp
60+
}
61+
}
62+
dp[mid] = [2]int{index, res}
63+
dfs(top, mid-1, left, index)
64+
dfs(mid+1, bottom, index, right)
65+
}
66+
67+
dfs(0, H-1, 0, W-1)
68+
return dp
69+
}
70+
71+
//
72+
//
73+
//
74+
// 给你一个房屋数组houses 和一个整数 k ,
75+
// 其中 houses[i] 是第 i 栋房子在一条街上的位置,
76+
// 现需要在这条街上安排 k 个邮筒。
77+
// 请你返回每栋房子与离它最近的邮筒之间的距离的 最小 总和。
78+
// !dp[k][n] 表示将[1,n]分成k段时的最优解
79+
// !dp[i][j]=min(dp[i-1][k]+f(k,j)) (k<j) f是一个Monotone函数
80+
func minDistance(houses []int, k int) int {
81+
sort.Ints(houses)
82+
83+
// 求距离的函数
84+
n := len(houses)
85+
memo := make([][]int, n+1)
86+
for i := range memo {
87+
memo[i] = make([]int, n+1)
88+
for j := range memo[i] {
89+
memo[i][j] = -1
90+
}
91+
}
92+
var dist func(i, j int) int
93+
dist = func(i, j int) int {
94+
if i >= j {
95+
return 0
96+
}
97+
if memo[i][j] != -1 {
98+
return memo[i][j]
99+
}
100+
res := houses[j] - houses[i] + dist(i+1, j-1)
101+
memo[i][j] = res
102+
return res
103+
}
104+
105+
dist2 := func(i, j int) int {
106+
return dist(i, j-1)
107+
}
108+
dp := divideAndConquerOptimization(k, n, dist2)
109+
return dp[k][n]
110+
}
111+
112+
func main() {
113+
// houses = [7,4,6,1], k = 1
114+
fmt.Println(minDistance([]int{7, 4, 6, 1}, 1))
115+
}
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
// 决策单调性(Monotone)
2+
// Monge(四边形不等式) ⇒ Totally Monotone(TM) ⇒ Monotone なので、Monotone は弱い条件である。
3+
// https://ei1333.github.io/luzhiled/snippets/dp/monotone-minima.html
4+
// https://beet-aizu.github.io/library/algorithm/monotoneminima.cpp
5+
6+
// 对于一个二元函数f(i,j) (0<=i<H, 0<=j<W),
7+
// !如果对任意 p<q 满足 argmin(f(p,*))<=argmin(f(q,*)),
8+
// !即f(i,j)取到最小值时,j如果变大,i也变大,则称f(i,j)是关于i Monotone 的.
9+
// !例如 f(i,j)=nums[j]+(j-i)^2 是关于i的Monotone函数(一次函数)
10+
11+
package main
12+
13+
import (
14+
"bufio"
15+
"fmt"
16+
"os"
17+
)
18+
19+
// !dist(i,j): 闭区间[i,j]的代价(0<=i<=j<=W-1)
20+
func monotoneminima(H, W int, dist func(i, j int) int) [][2]int {
21+
dp := make([][2]int, H) // dp[i] 表示第i行取到`最小值`的(索引,值)
22+
23+
var dfs func(top, bottom, left, right int)
24+
dfs = func(top, bottom, left, right int) {
25+
if top > bottom {
26+
return
27+
}
28+
29+
mid := (top + bottom) / 2
30+
index := -1
31+
res := 0
32+
for i := left; i <= right; i++ {
33+
tmp := dist(mid, i)
34+
if index == -1 || tmp < res { // less
35+
index = i
36+
res = tmp
37+
}
38+
}
39+
dp[mid] = [2]int{index, res}
40+
dfs(top, mid-1, left, index)
41+
dfs(mid+1, bottom, index, right)
42+
}
43+
44+
dfs(0, H-1, 0, W-1)
45+
return dp
46+
}
47+
48+
func main() {
49+
// AtCoder COLOCON -Colopl programming contest 2018- Final C - スペースエクスプローラー高橋君
50+
// https://blog.hamayanhamayan.com/entry/2018/01/21/161336
51+
// 给定长为n的数组a (n<=2e5)
52+
// !对每个i 求 a[j]+(j-i)^2 的最小值
53+
// 也可以用Convex Hull Trick 求出
54+
55+
in := bufio.NewReader(os.Stdin)
56+
out := bufio.NewWriter(os.Stdout)
57+
defer out.Flush()
58+
59+
var n int
60+
fmt.Fscan(in, &n)
61+
nums := make([]int, n)
62+
for i := 0; i < n; i++ {
63+
fmt.Fscan(in, &nums[i])
64+
}
65+
66+
dist := func(i, j int) int {
67+
return nums[j] + (j-i)*(j-i)
68+
}
69+
res := monotoneminima(n, n, dist)
70+
for i := 0; i < n; i++ {
71+
fmt.Fprintln(out, res[i][1])
72+
}
73+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
offlineDp -> onlineDp (オフライン・オンライン変換)
2+
3+
- offlineDp 可以理解为区间分 k 组的这种 dp
4+
dp[k][j]=min(dp[k-1][j]+f(i,j)) (i<j)
5+
6+
- 如果代价函数 f(i,j) 是决策单调(Monotone)的,那么可以用分治 dp 优化,时间复杂度从 `O(n^2*k)` 降到 `O(n*logn*k)`
7+
> 备注: Monge ⇒ Totally Monotone(TM) ⇒ Monotones,即满足四边形不等式的函数一定满足决策单调性
8+
9+
- onlineDp 可以理解为不分组的这种 dp
10+
dp[j]=min(dp[i]+f(i,j)) (i<j)
11+
- 如果 offline 问题存在复杂度 O(M(n))的解,那么 online 问题存在复杂度 O(M(n)logn)的解
12+
- 如果 f 可以拆成 i 的一次函数,那么可以用 CHT(ConvexHullTrick) 优化 dp
Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
// offline dp: 区间分成k等分 dp[k][j]=min(dp[k-1][i]+f(i,j)) (0<=i<j) (dfsIndexRemain)
2+
// online dp: 区间分成任意分 dp[j]=min(dp[i]+f(i,j)) (0<=i<j) (dfsIndex)
3+
4+
// !https://qiita.com/tmaehara/items/0687af2cfb807cde7860 (深度好文)
5+
// https://beet-aizu.github.io/library/algorithm/offlineonline.cpp
6+
// https://ei1333.github.io/library/dp/online-offline-dp.hpp
7+
8+
// オフライン・オンライン変換:
9+
// !如果offline问题存在复杂度O(M(n))的解,那么online问题存在复杂度O(M(n)logn)的解
10+
// dp[j]=min(dp[i]+f(i,j)) (0<=i<j)
11+
// O(n^2)优化到O(nlogn^2)
12+
// 例子:
13+
// !dp[j]=min(dp[i]+(x[j]-x[i]-a)^2)
14+
15+
package main
16+
17+
import (
18+
"bufio"
19+
"fmt"
20+
"os"
21+
)
22+
23+
// !dist(i,j): 左闭右开区间[i,j)的代价(0<=i<j<=n)
24+
func offlineOnlineDp(n int, dist func(i, j int) int) int {
25+
dp := make([]int, n+1)
26+
used := make([]bool, n+1)
27+
used[n] = true
28+
29+
update := func(k, val int) {
30+
if !used[k] {
31+
dp[k] = val
32+
}
33+
dp[k] = min(dp[k], val) // min if get min
34+
used[k] = true
35+
}
36+
37+
var dfs func(top, bottom, left, right int) // induce
38+
dfs = func(top, bottom, left, right int) {
39+
if top == bottom {
40+
return
41+
}
42+
mid := (top + bottom) / 2
43+
index := left
44+
res := dist(mid, index) + dp[index]
45+
for i := left; i <= right; i++ {
46+
tmp := dist(mid, i) + dp[i]
47+
if tmp < res { // !less if get min
48+
res = tmp
49+
index = i
50+
}
51+
}
52+
53+
update(mid, res)
54+
dfs(top, mid, left, index)
55+
dfs(mid+1, bottom, index, right)
56+
}
57+
58+
var solve func(left, right int)
59+
solve = func(left, right int) {
60+
if left+1 == right {
61+
update(left, dist(left, right)+dp[right])
62+
return
63+
}
64+
mid := (left + right) / 2
65+
solve(mid, right)
66+
dfs(left, mid, mid, right)
67+
solve(left, mid)
68+
}
69+
70+
solve(0, n)
71+
return dp[0]
72+
}
73+
74+
func min(a, b int) int {
75+
if a < b {
76+
return a
77+
}
78+
return b
79+
}
80+
81+
func main() {
82+
// 垃圾回收
83+
// https://yukicoder.me/problems/no/705
84+
// 公园里有n个垃圾
85+
// n个人准备去回收垃圾,
86+
// 每个人的起点在(startsi,0),沿x轴顺序递增排列
87+
// 每个垃圾的位置在(xi,yi),沿x轴顺序递增排列
88+
// !每个人只能回收他左边的垃圾
89+
// 每个人i回收垃圾j,j+1,...,i(0<=j<=i)的时间花费为
90+
// 1.曼哈顿距离的平方2.曼哈顿距离3.曼哈顿距离的三次方
91+
// !求回收所有垃圾的最短时间之和
92+
// dp[i]表示前i个人回收完前i个垃圾时的最短花费
93+
// dp[i]=min(dp[j]+abs((xi-xj))^2+yj^2) (0<=j<=i)
94+
95+
in := bufio.NewReader(os.Stdin)
96+
out := bufio.NewWriter(os.Stdout)
97+
defer out.Flush()
98+
99+
var n int
100+
fmt.Fscan(in, &n)
101+
starts, xs, ys := make([]int, n), make([]int, n), make([]int, n)
102+
for i := 0; i < n; i++ {
103+
fmt.Fscan(in, &starts[i])
104+
}
105+
for i := 0; i < n; i++ {
106+
fmt.Fscan(in, &xs[i])
107+
}
108+
for i := 0; i < n; i++ {
109+
fmt.Fscan(in, &ys[i])
110+
}
111+
112+
dist := func(i, j int) int {
113+
// 0<=i<j<=n
114+
a := abs(starts[j-1] - xs[i])
115+
b := abs(ys[i])
116+
return a + b
117+
}
118+
res := offlineOnlineDp(n, dist)
119+
fmt.Fprintln(out, res)
120+
}
121+
122+
func abs(x int) int {
123+
if x < 0 {
124+
return -x
125+
}
126+
return x
127+
}

0 commit comments

Comments
 (0)