6

I've been thinking a lot about the following problem:

We are given an array of n numbers. We start at the first index and our task is to get to the last index. Every move we can jump one or two steps forward and the number at the index we jump to represents the cost we need to pay for visiting that index. We need to find the cheapest way for getting to the end of the array.

For example if the array looks like this: [2,1,4,2,5] the cheapest way to get to the end is 10: we visit the indexes 1->2->4->5 and we have to pay 2+1+2+5 = 10 which is the cheapest possible way. Let f(i) be the cheapest price to get to the index i. This we can calculate easily in O(n) time with dynamic programming by realizing that f(i) = arr[i] + min(f(i-1),f(i-2))

But here's the twist: The array gets updated several times and after every update we need to be able to tell in O(logn) time what is the cheapest way at the moment. Updating the array happens by telling the index which will be changed and the number it will be changed to. For example the update could be arr[2] = 7 changing our example array to [2,7,4,2,5]. Now the cheapest way would be 11.

Now how can we support these updates in O(logn) time? Any ideas?

Here's what I've come up with so far: First I would create an array f for the dynamic programming like described before. I would store the content of this array in a segment tree s in the following way: s(i) = f(i) - f(i-1). This would allow me to update intervals of f (adding a constant to every value) in O(logn) time and ask for the values at a given index in O(logn) time. This would come handy since after some updates it often happens that all the values in f would need to be increased by a constant after some given index in f. So by asking for the value of s(n) in the segment tree after every update we would get the answer we need.

There are however different things that can happen after an update:

  1. Only one value in f needs to be updated. For exampe if [2,1,4,2,5,4,9,3,7] gets changed to [2,1,9,2,5,4,9,3,7] only f(3) would need to be updated, since no cheapest way went through the 3. index anyway.
  2. All the values in f after a given index need to be updated by a constant. This is what the segment tree is good for.
  3. Every other value in f after a given index needs to be updated by a constant.
  4. Something more random.
Henry
  • 42,982
  • 7
  • 68
  • 84
tykkipeli
  • 146
  • 9
  • 1
    @SamerTufail You would need to prove that there cannot be an update which create a new shortest path without touching any node on the previous shortest path. – fjardon Sep 27 '18 at 09:57
  • @fjardon thanks, I missed that part. – Samer Tufail Sep 27 '18 at 09:58
  • Could you provide the link to original problem? – Pham Trung Sep 27 '18 at 10:06
  • 1
    Sure, but the original problem was in my mother tongue in finnish, so you won't understand too much. https://cses.fi/alon18/task/1247 You can find here the limits and the example inputs and outputs however – tykkipeli Sep 27 '18 at 10:42
  • The original question statement does not mention `log n` time complexity. Was it your own deduction that this could be done in logarithmic time? – meowgoesthedog Sep 27 '18 at 10:44
  • Well anything significantly slower than O(logn) isn't fast enough. The array size in the tests is 200000 and there are 200000 updates – tykkipeli Sep 27 '18 at 10:46
  • I think performance should depend not only on *n*, but also on the changed number. if 1 was changed to 10^6 it's likely that more time would be needed than if 1 was changed to 2. – Yola Sep 27 '18 at 14:05
  • The numbers in the array are anything from 1 to 10^9 and they can change from any number to any other number. Yes, it is certainly true that the performance of some (bad) algorithms heavily depend on what is changed and how. I got a tip from a reliable source (the creator of the problem) that after every update the new lowest cost can indeed be figured out in **O(logn)** time and this doesn't depend on what is changed or how much. – tykkipeli Sep 27 '18 at 16:57
  • 1
    This problem can be solved by using [Dynamizing Dijkstra](https://www.sciencedirect.com/science/article/pii/S1319157817303828), don't know whether a less complex solution exists. – Pham Trung Sep 28 '18 at 07:04

1 Answers1

3

Alright, I managed to solve the problem all myself so I decided to share the solution with you. :)

I was on the right track with dynamic programming and segment tree, but i was feeding the segment tree in a wrong way in my previous attempts.

Here's how we can support the updates in O(logn) time: The idea is to use a binary segment tree where the leaves of the tree represent the current array and every node stores 4 different values.

  1. v1 = The lowest cost to get from the leftmost descendant to the rightmost descendant
  2. v2 = The lowest cost to get from the leftmost descendant to the second rightmost descendant
  3. v3 = The lowest cost to get from the second leftmost descendant to the rightmost descendant
  4. v4 = The lowest cost to get from the second leftmost descendant to the second rightmost descendant

With descendants I mean the descendants of the node that are also leaves.

When updating the array we update the value at the leaf and then all its ancestors up to the root. Since at every node we already know all of these 4 values of its two children, we can calculate easily the new 4 values for the current parent node. Just to give an example: v1_current_node = min(v2_leftchild+v1_rightchild, v1_leftchild+v1_rightchild, v1_leftchild+v3_rightchild). All the other three values can be calculated in a similar way.

Since there are only O(logn) ancestors for every leaf, and all 4 values are calculated in O(1) time it takes only O(logn) time to update the entire tree.

Now that we know the 4 values for every node, we can in a similar way calculate the lowest cost from the first to the n:th node by using the nodes of the highest powers of 2 in our path in the tree that add up to n. For example if n = 11 we want to know the lowest cost from first to eleventh node and this can be done by using the information of the node that covers the leaves 1-8, the node that covers the leaves 9-10 and the leaf node 11. For all of those three nodes we know the 4 values described and we can in a similar way combine that information to figure out the answer. At very most we need to consider O(logn) nodes for doing this so that is not a problem.

גלעד ברקן
  • 23,602
  • 3
  • 25
  • 61
tykkipeli
  • 146
  • 9
  • If we have 16 elements, for example, the second level from the leaves knows elements `[1-4, 5-8, 9-12, 13-16]` , the next level up knows `[1-8, 9-16]`. But these are disjoint sets. If we know the shortest cost from 1 to 7 or 8 and update one of the leaves between 1 and 8, the node that stores information about 9-16 does not get updated. But how can we know the cost of getting to 9 without knowing the cost to 7 or 8? The tree will provide inaccurate information if the updates are only along the disjoint set. Please explain how this is resolved if I am misunderstanding. – גלעד ברקן Sep 28 '18 at 10:59
  • Did you read my example how to calculate v1? Do you understand why that formula is true? Yes there is a node resposible for 1-8 and also one responsible for 9-16. Now assume you already now all the 4 values for both of those child nodes. Now how can we figure out the 4 values for the node responsible dor 1-16? Let's say we want to now what the value of v1 is for that node, that is the lowest cost path from leaf 1 to leaf 16. – tykkipeli Sep 28 '18 at 11:14
  • Now there are three possible paths that it can be. Either we take the cheapest path from 1 to 7 (that is v2 of the left child) then we jump to leaf 9 and then we take the cheapest path from 9 to 16 (that is v1 of the right child), OR we take the cheapest path from 1 to 8, jump to 9 and take the cheapest path from 9 to 16 OR we take the cheapest path from 1 to 8, jump to 10 and then take the cheapest path from 10 to 16. The cheapest possible way from 1 to 16 is with 100% certainty one of those paths so we just take the minimum. – tykkipeli Sep 28 '18 at 11:14
  • Ah, so we're saying the costs *are* actually disjoint. We can use path segments and connect them without depending on earlier segments. Interesting. I'll think about that, thanks. – גלעד ברקן Sep 28 '18 at 11:26