2

Recently I have encountered what seems to be a quite interesting game that suggest implementing both two-pointers and prefix-sum techniques on a large dataset. Here is the task itself:

Imagine there is an array v of length k where k (k<=10**5) represents the amount of entries consisting of two numbers: A (so-called pile) and N(the amount), for example:

k = 3
v = [[2, 2], [3, 2], [2, 1]]

A (v[i][0]) is the value and N (v[i][1]) is the number of times this value is given. In other words, N is the frequency of A.

Now, what we have to do is to choose the minimum A, starting from both ends of the list, subtract it from the current positions and add it to the following ones. Then we continue doing so before there are only two or one etries left in the middle. In other words, each step we choose the smallest pile and substract it from both ends until one or two piles left.

The answer should include two lines: the first contains either '1' or '2' depending on how much piles are left, and the second is the result itself.

To simplify, we can represent piles as a plain list that looks like this:

v = [2, 2, 3, 3, 2] 

We choose the minimum from the left and the right ends (2), substract it and add to the next values:

v = [0, 4, 3, 5, 0]

The minimum of 4 and 5 is 4, so we substract 4 and get two piles.

v = [0, 0, 11, 1, 0]

And the output is:

2
11 1

My code is:

k = int(input())
a = []
for _ in range(k):
    A, N = map(int, input().split())
    x = [A] * N
    a += x


l = 0
r = len(a)-1
while r - l > 1:
    s = min(a[l], a[r])
    a[l] -= s
    a[r] -= s
    a[l+1] += s
    a[r-1] += s
    if a[l] == 0:
       l += 1
    if a[r] == 0:
       r -= 1

if a[r] == 0 or a[r]==a[l]:
    print(1)
    print(a[l])
      
else:
    print(2)
    print(a[l], a[r])

This solution fails the time limit (2sec) with k = 10**5 because it requires converting an array into a plain list and also barely employs prefix-sum. I know for sure that there is a much faster solution with prefix-sum, additional variables and storing an array as given. Would be grateful for any advice on how I can speed up this code.
To prevent any language-related comments: the game is given for any programming language so the time limit is not specifically python's issue.

Ramiil
  • 59
  • 8
  • 2
    Try an approach that doesn't involve expanding the array; it should still use the same algorithm you've implemented (two pointers on the left and the right) but would require a little more care in the increment/subtract operations. – wLui155 Nov 01 '21 at 18:32

1 Answers1

2

I won't give you the answer, but I will give a path to reason out the answer.

The trick isn't to perform the algorithm faster, it is to figure out the result of applying the algorithm while doing less work. To make that easier, I'm going to replace the algorithm with an even slower one, which happens to be easier to reason about.

Rather than subtract the smaller from both sides, adding to the next, let's just subtract 1 from each side, adding to the next. So your example would start off:

[2, 2, 3, 3, 2]
[1, 3, 3, 4, 1]
[0, 4, 3, 5, 0]
...

Let's start reasoning.

First suppose that we have A on one end, how many steps does it take to advance that side by one? The answer is simply A.

What happens if we have A followed by k as? To advance through the block until everything is piled on the last element takes

A + A+a + A+2a + ... A+(k-1)a
    = kA + (1 + 2 + ... + (k-1))a
    = kA + k(k-1)a/2

At which point that last element now has A + ka elements.

So now, without expanding a block, we can figure out how much work it takes to march through it with a simple formula. We can do this on the other side as well. So, without expanding, we can start consuming whole blocks on each side until we meet the middle on the last 2 or 3 blocks. We then have to be careful, but we can figure out exactly where one is when the other side completes a block. Then we can split those blocks into smaller blocks elements and keep advancing until we find at most 3 array elements between our advancing fronts where they will meet. And then we apply the original algorithm a few times to get the exact answer.

But you have to be able to go through entire blocks with a single arithmetic calculation in order to make this fast enough.

Good luck getting all of the boundary conditions right!

btilly
  • 43,296
  • 3
  • 59
  • 88
  • I guess it is no coincidence that your formula reminds me of an arithmetic progression. I will try to work it around and post the answer here, thanks a lot! – Ramiil Nov 02 '21 at 09:08