From 5f60a31bc7524be77a92c009698e204958904208 Mon Sep 17 00:00:00 2001 From: fer22f <fer22f@gmail.com> Date: Tue, 31 Mar 2020 00:47:29 -0300 Subject: [PATCH] Add Algorithms Live notes --- .../Algorithms-Live-14-Exchange-Arguments.md" | 140 ++++++++++ .../Algorithms-Live-15-Split-the-Sequence.md" | 248 ++++++++++++++++++ 2 files changed, 388 insertions(+) create mode 100644 "anota\303\247\303\265es/algorithms-live/Algorithms-Live-14-Exchange-Arguments.md" create mode 100644 "anota\303\247\303\265es/algorithms-live/Algorithms-Live-15-Split-the-Sequence.md" diff --git "a/anota\303\247\303\265es/algorithms-live/Algorithms-Live-14-Exchange-Arguments.md" "b/anota\303\247\303\265es/algorithms-live/Algorithms-Live-14-Exchange-Arguments.md" new file mode 100644 index 0000000..a967db2 --- /dev/null +++ "b/anota\303\247\303\265es/algorithms-live/Algorithms-Live-14-Exchange-Arguments.md" @@ -0,0 +1,140 @@ +# Exchange Arguments + +# Working out a solution comparing it to every other solution +Given n. Pick a and b such that a + b = n and maximize ab. + +Case n is even. Let's prove that a = b = x = n/2 is the best solution. + We then have ab = x^2. + Let's assume that a is the biggest number such that: + a = x + delta + b = x - delta + All other solutions are where delta >= 1. + `ab = (x + delta)(x - delta) = x^2 + delta*x - delta*x - delta^2 + = x^2 - delta^2 < x^2` + (because delta is a position number) + +# The exchange problem +Generally, greedy doesn't work for coin exchanges, for example: + +Get to 1000 with coins (502) (499) (1) +We get (502) + (1)*498 with greedy, instead of (499)*2 + (1)*2. + +# Probing +Let's try these coins: (25) (10) (5) (1). + +## What properties the optimal solution has + +Suppose we have 3*(10), we can replace it with (25) + (5). +So the max number of nickels is 2. The optimal solution, then, never has +more than 2 nickels. + +Suppose we have 2*(5), we can replace it with (10). +So the max number of dimes is 1. The optimal solution, then, never has more +than 1 dime. + +Suppose 5*(1), we can replace it with (5). So at most 4 pennies. + +Suppose 2*(10) + 1*(5), but we can replace it with (25). So we can't have +both. + +Anything that less than (25), amounts to 24 or less. Let's do it by cases: + * Max number of each: (10)*2 + (1)*4 = 24. + * Max number with 10: (10)*1 + (5)*1 + (1)*4 = 19. + +## Proof +O*: Optimal solution +G: Greedy solution + +Let q = number of (25) in O*. + q' = number of (25) in G. +Greedy guarantees that q > q', it will be the same or worse, so q `<=` q'. +Suppose q `<` q'. However, we have at most 24 using the other coins, so we +can't replace them with a (25), thus q = q'. + +Let d = number of (10) in O*. + d' = number of (10) in G. +Suppose d `<` d'. We can't use the other coins to make 10, we can have at +most 1*(5) + 4*(1). Thus, d = d'. + +Same argument with nickels (5), and pennies (1). + +Thus, O* = G. + +# Minimizing dot product +``` +v_1 = <-3, 4, 2> +v_2 = <1, -4, 11> +``` +Rearrange the numbers such that the dot product is minimized. + +dot = \sum^{k}_{i=0} v_1[i] v_2[i] + = -3 * 1 - 4 * 4 + 2 * 11 + = -3 - 16 + 22 + = 3. + +Sort the first one, because it doesn't matter. +``` +v_1 = <-3, 2, 4> +``` +Then think of an arrangement for `v_2`. + +What if we match the biggest things with the smaller things? +``` +v_1 = <-3, 2, 4> +v_2 = <11, 1, -4> + +-33 + 2 - 16 = -47 +``` +Does this always work? (Yes). + +``` +x_i <= x_j for every i < j +y_i < y_j <=> swap y_i, y_j +``` + +Let's say.... +``` + x_i*y_i + x_j*y_j - (x_i*y_j + x_j*y_i) += x_i*y_i + x_j*y_j - x_i*y_j - x_j*y_i += x_i(y_i - y_j) + x_j(y_j - y_i) += x_i(y_i - y_j) + x_j(-1)(y_i - y_j) += (x_i - x_j)(y_i - y_j) +``` + +Where `x_i - x_j <= 0` and `y_i - y_j < 0`, which means +`(x_i - x_j)(y_i - y_j) >= 0`. +So we can conclude that swapping decreases or maintains the same cost (for +something that doesn't meet the property of being ordered decreasingly). + +# Solving "Problems" +A problem is defined by the tuple (reward, decay (in seconds), time needed to solve). +Maximize reward, which problems do you solve and at what order? + +## Simplification +What if we knew which problems to solve? We need to only find the solve order. + +Tip: Explore the cost exchange to find the property to sort when 2 problems swap. + +So we have P_1 and P_2. + +reward_1 = m_1 - d_1*t_1 + m_2 - d_2(t_1 + t_2) +reward_2 = m_2 - d_2*t_2 + m_1 - d_1(t_1 + t_2) + +When does `reward_1 >= reward_2`? It would be when `reward_1 - reward_2 >= 0`. + +So let's do algebra: + +``` + m_1 - d_1*t_1 + m_2 - d_2*t_1 + d_2*t_2 +- m_2 + d_2*t_2 - m_1 + d_1*t_1 + d_1*t_2 += d_1*t_2 - d_2*t_1 >= 0 + d_1*t_2 >= d_2*t_1 +``` + +So my sorting criteria is `d_2*t_1 <= d_1*t_2`, this is the optimal ordering. + +## Steps to solve +1) Sort by optimal criteria +2) Run dynamic programming solution to know which problems to take + State: which problem, time used + Transitions: solve problem, skip problem diff --git "a/anota\303\247\303\265es/algorithms-live/Algorithms-Live-15-Split-the-Sequence.md" "b/anota\303\247\303\265es/algorithms-live/Algorithms-Live-15-Split-the-Sequence.md" new file mode 100644 index 0000000..1052557 --- /dev/null +++ "b/anota\303\247\303\265es/algorithms-live/Algorithms-Live-15-Split-the-Sequence.md" @@ -0,0 +1,248 @@ +# Split the Sequence +Given k, partition an array to maximize the sum of the multiplication of the sum of the values at each step. +For example, for k = 3, the optimal is: + +4 1 3 4 0 2 3 +4 | 1 3 4 0 2 3 4*13 = 52 +4 | 1 3 | 4 0 2 3 4*9 = 36 +4 | 1 3 | 4 0 | 2 3 4*5 = 20 +Result = 108 + +## Approach 1 +The bounds are `1 <= k < n <= 10`. So we could have a solution in `n!`. + +## Approach 2 +The bounds are `1 <= k < n <= 50`. Let's use MCM (Matrix Chain Multiplication) +style DP. Or parenthesis placement style DP. + +( 4 1 3 2 ) ( 4 5 6 1 ) +( ( 4 1 ) ( 3 2 ) ) ( 4 5 6 1 ) + +Let's place the outer parenthesis first, then gradually bind towards the +inner parenthesis. So: + +* State: Left side, right side (what the range of the values being parenthesized), k (how many splits left) +* Transitions: + * Brute force split point (all n possible split points) + * Brute force k distribution also, (k_1) and (k_2) where k_2 = k - k_1. + +State size: O(n^2 k). +Transitions: O(n k). +DP is O(n^3 k^2) which works up to 50. + +```java +long go(int i, int j, int k) { + if (i == j) return 0L; + + if (memo[i][j][k] != null) + return memo[i][j][k]; + + long res = 0; + long sumTotal = 0; + long sumLeft = 0; + for (int m = i; m <= j; m++) + sumTotal += vs[m]; + + for (int m = i; m < j; m++) { + sumLeft += vs[m]; + long sumRight = sumTotal - sumLeft; + // every split point + for (int k1 = 0; k1 < k; k1++) { + int k2 = k - k1-1; + long splitCost = sumLeft * sumRight; + + long r1 = go(i, m, k1); + long r2 = go(m+1, j, k2); + + long rr = r1 + r2 + splitCost; + res = Math.max(res, rr); + } + } + + return memo[i][j][k] = res; +} + +public B(in, out) { + memo = new Long[N][N][K+1]; + long res = go(0, N-1, K); +} +``` + +## Approach 3 +Greedy Observation: The order of the splits doesn't matter. + +Let's prove it: + +A | B | C + 1 2 + 2 1 + +Two splits, three groups of numbers A, B, C. We want to prove that + +``` +(AB)C = A(BC) +``` + +The cost of doing the first split first is `A(B + C) + BC = AB + AC + BC`. +The cost of doing the second split first is `(A + B)C + AB = AB + AC + BC`, +which are equal. + +So let's make the splits in order! + +So we have a new DP: +* State: Start of the sequence we are at, which k is left +* Transitions: All ending points + +So we get O(n^2 k). + +We are expected to pass these bounds: `1 <= n <= 1000`, `1 <= k <= min(n-1, 200)`. + +```java +long go(int i, int k) { + if (k == -1 || i == N) + return 0L; + + if (memo[i][k] != null) + return memo[i][k]; + + long res = 0; + for (int j = i; j < N; j++) { + long splitCost = sum[i] * (sum[j+1] - sum[i]); + long rr = splitCost + go(j+1, k-1); + res = Math.max(res, rr); + } + + return memo[i][k] = res; +} + +public B(in, out) { + memo = new Long[N][K+1]; + sum = new int[N+1]; + for (int i = 0; i < N; i++) + sum[i+1] = sum[i] + vs[i]; + long res = go(0, K); +} +``` + +## Approach 4 +Bounds of `n <= 10^5, k <= min(n-1, 200)`. + +Let's make the splits in reverse order: +``` +------ splits = b + | | x -> | + j i +------ sums = a +``` +The cost of making splits up to and including `i` is `b`. +The sum of the range up to and including `i` is `a`. +And there is some sum of costs that is being propagated to the right, `x`. + +So when we make a split at position i from right to left, we sum `ax` to the cost, +and the result is summed to whatever splits we make to the left, `b`, so we +get `ax + b`, which is a line. So this is Convex Hull Optimization. +We handle k by computing k and then k + 1. + +```java +public B(in, out) { + memo = new Long[N][K+1]; + sum = new int[N+1]; + for (int i = 0; i < N; i++) + sum[i+1] = sum[i] + vs[i]; + + // Convex Hull Optimization + long[] prev = new long[N+1]; + long[] cost = new long[N+1]; + + Line[] dq = new Line[N+1]; + int[][] prevSplit = new int[K][N+1]; + + // brute-force all posible k splits + for (int k = 0; k < K; k++) { + Arrays.fill(cost, 0); + int fptr = 0, bptr = 0; + + for (int i = 0; i <= N; i++) { + while (fptr+1 < bptr && dq[fptr].getCost(sum[i]) <= dq[fptr+1].getCost(sum[i])) + fptr++; + + if (fptr < bptr) { + cost[i] = dq[fptr].getCost(sum[i]); + // Save optimal split + prevSplit[k][i] = dq[fptr].i; + } + + Line newLine = new Line(i, sum[i], prev[i]); + + while (fptr + 1 < bptr) { + long t1 = dq[bptr-2].to(dq[bptr-1]); + long t2 = dq[bptr-1].to(newLine); + + if (t1 < t2) + break; + else + bptr--; + } + + dq[bptr++] = newLine; + } + + for (int i = 0; i <- N; i++) + prev[i] = cost[i]; + } + + out.println(prev[N]); + + int i = N; + int k = K-1; + ArrayDeque<Integer> stk = new ArrayDeque<>(); + while (k >= 0) { + i = preSplit[k][i]; + stpk.push(i); + k--; + } + + StringBUilder sb = new StringBuilder(); + while (stk.size() > 0) { + sb.append(stk.pop(); + sb.appenbd(' '); + } + out.println(sb.toString().trim()); +} + +class Line { + long a, b; + long i; + + Line (int ii, long aa, long bb) { + i = ii; a = aa; b = bb; + } + + // x is going to be prefix sum, so we just subtract a + long getCost(long x) { + return a * (x - a) + b; + } + + // when a line gets overtaken by another line + long to(Line rhs) { + // a line start at the position a + long lo = Math.max(a, rhs.a); + long hi = Integer.MAX_VALUE; + + while (lo < hi) { + long m = (lo+hi) / 2; + long v1= getCost(m); + long v2 = rhs.getCost(m); + + if (v1 <= v2) + hi = m; + else + lo = m+1; + } + + return hi; + } +} +``` + +Binary search there is not optimal, can be optimized. -- GitLab