Business
Jobs
  • About Us
  • Solutions
    • Job Postings
      Post your job and receive qualified candidates in 48h.
    • Candidate Assessments
      500+ technical and psychological tests, plus anti-fraud.
    • Headhunting
      Tailor-made executive search from start to finish.
    • Payroll + EOR
      Payroll dispersal and EOR across 15+ LATAM countries.
  • Pricing
  • Jobs

0

204
Views
Reversing Lists Splices Python Optimization (USACO February 2020 Bronze Question 3 "Swapity Swap")

I am trying to solve a problem that involves reversing list splices, and I am having trouble with the time limit for a test case,, which is 4 seconds. The question:

Farmer John's N cows (1≤N≤100) are standing in a line. The ith cow from the left has label i, for each 1≤i≤N. Farmer John has come up with a new morning exercise routine for the cows. He tells them to repeat the following two-step process exactly K (1≤K≤1000000000) times:

The sequence of cows currently in positions A1…A2 from the left reverse their order (1≤A1<A2≤N). Then, the sequence of cows currently in positions B1…B2 from the left reverse their order (1≤B1<B2≤N). After the cows have repeated this process exactly K times, please output the label of the ith cow from the left for each 1≤i≤N.

SCORING: Test cases 2-3 satisfy K≤100. Test cases 4-13 satisfy no additional constraints.

INPUT FORMAT (file swap.in): The first line of input contains N and K. The second line contains A1 and A2, and the third contains B1 and B2.

OUTPUT FORMAT (file swap.out): On the ith line of output, print the label of the ith cow from the left at the end of the exercise routine.

SAMPLE INPUT:

7 2
2 5
3 7

SAMPLE OUTPUT:

1
2
4
3
5
7
6

Initially, the order of the cows is [1,2,3,4,5,6,7] from left to right. After the first step of the process, the order is [1,5,4,3,2,6,7]. After the second step of the process, the order is [1,5,7,6,2,3,4]. Repeating both steps a second time yields the output of the sample.

Theoretically, you could solve this problem by finding the point where the program repeats, and then simulating the reverse k % frequency times, where frequency is the amount of times the simulation is unique. But my problem is that when the input is:

100 1000000000
1 94
2 98

my program takes over 100 seconds to run. This input is particularly time consuming because it runs the maximum number of iterations, and frequency is very high.

Current Code:

fin = open("swap.in", 'r')
line = fin.readline().strip().split()
n = int(line[0])
k = int(line[1])
nums = [[int(x)-1 for x in fin.readline().strip().split()]for i in range(2)]
fin.close()
repeated = []
cows = [i for i in range(1, n+1)]
repeat = False
while not repeat:
    for i in nums:
        cows[i[0]:i[1]+1] = reversed(cows[i[0]:i[1]+1])
        if cows[i[0]:i[1]+1] in repeated :
            frequency = len(repeated)-1
            repeat = True
        repeated.append(cows[i[0]:i[1]+1])

cows = [i for i in range(1, n+1)]
for _ in range(k%frequency):
    for i in nums:
        cows[i[0]:i[1]+1] = reversed(cows[i[0]:i[1]+1])

fout = open("swap.out", 'w')
for i in cows:
    fout.write(str(i) + "\n")
fout.close()

If anyone knows a way to solve this issue, please post an answer. Comment if anything isn't clear.

over 4 years ago · Santiago Trujillo
5 answers
Answer question

0

The main problem with the performance of your code is that you're using a list to keep a history of cow positions in each iteration in order to detect a cycle, which requires O(n) for each membership lookup with the in operator.

Instead, you can use a set for the purpose, which costs O(1) in membership lookups. But since you still need to then iterate k % i times, where i is the length of the cycle, to get to that specific position in the cycle, it would be better if the set is ordered so you can simply get the (k % i)-indexed entry in the set instead of having to perform that many times of reversals. But since set is unordered in Python, you can instead use a dict, where the keys are ordered since Python 3.6:

from itertools import islice

n, k, a1, a2, b1, b2 = map(int, '''100 1000000000
1 94
2 98'''.split())
cows = list(range(1, n + 1))
history = {}
for i in range(k):
    key = tuple(cows)
    if key in history:
        cows = next(islice(history, k % i, None))
        break
    history[key] = 1
    for bound in map(slice, (a1 - 1, b1 - 1), (a2, b2)):
        cows[bound] = reversed(cows[bound])
print(*cows, sep='\n')

This outputs:

71
2
3
74
10
76
7
8
79
15
81
12
13
84
20
86
17
18
89
25
91
22
23
94
30
96
27
28
1
35
4
32
33
6
40
9
37
38
11
45
14
42
43
16
50
19
47
48
21
55
24
52
53
26
60
29
57
58
31
65
34
62
63
36
70
39
67
68
41
75
44
72
73
46
80
49
77
78
51
85
54
82
83
56
90
59
87
88
61
95
64
92
93
66
5
69
97
98
99
100

Demo: https://replit.com/@blhsing/CultivatedPointedArchitect

over 4 years ago · Santiago Trujillo Report

0

You can get rid of the look up. This computes the necessary 5680 swaps twice, but saves the extra space for the dictionary. On a google colab instance this solution runs 33% faster than @blhsing solution for this particular example.

n, k, a1, a2, b1, b2 = [int(x) for x in
    '''
    100 1000000000
    1 94
    2 98
    '''.split()]
a1 -= 1
b1 -= 1

cows = list(range(1, n+1))
rot = cows[:]
s = k

while k:
    rot[a1:a2] = reversed(rot[a1:a2])
    rot[b1:b2] = reversed(rot[b1:b2])
    k -= 1
    if rot == cows:
        print(f'found frequency {s-k}')
        k %= s-k

print(*rot)

Output

found frequency 29640
71 2 3 74 10 76 7 8 79 15 81 12 13 84 20 86 17 18 89 25 91 22 23 94 30 96 27 28 1 35 4 32 33 6 40 9 37 38 11 45 14 42 43 16 50 19 47 48 21 55 24 52 53 26 60 29 57 58 31 65 34 62 63 36 70 39 67 68 41 75 44 72 73 46 80 49 77 78 51 85 54 82 83 56 90 59 87 88 61 95 64 92 93 66 5 69 97 98 99 100

@btilly's approach

I coded his approach and got 4x faster runtime to @dillondavis' first solution (320 µs / 78.9 µs)

n, k, a1, a2, b1, b2 = [int(x) for x in
    '''
    100 1000000000
    1 94
    2 98
    '''.split()]
a1 -= 1
b1 -= 1
cows = list(range(n))
cows[a1:a2] = reversed(cows[a1:a2])
cows[b1:b2] = reversed(cows[b1:b2])

visited = set()
runs = []
for i in cows:
    if i not in visited:
        run = [i]
        nex = cows[i]
        while nex != i:
            run.append(nex)
            nex = cows[nex]
            visited.add(nex)
        runs.append(run)
for i in runs:
    r = k % len(i)
    for x,y in zip(i, i[r:]+i[:r]):
        cows[x] = y+1
print(*cows)

Output

71 2 3 74 10 76 7 8 79 15 81 12 13 84 20 86 17 18 89 25 91 22 23 94 30 96 27 28 1 35 4 32 33 6 40 9 37 38 11 45 14 42 43 16 50 19 47 48 21 55 24 52 53 26 60 29 57 58 31 65 34 62 63 36 70 39 67 68 41 75 44 72 73 46 80 49 77 78 51 85 54 82 83 56 90 59 87 88 61 95 64 92 93 66 5 69 97 98 99 100
over 4 years ago · Santiago Trujillo Report

0

Here is an approach that is O(N) work no matter what K might be.

Condensed explanation: Rewrite the permutation in cycle notation. Use cycle notation to generate the answer. (Except we don't need the full cycle notation.

To illustrate I'll use your example, but I'll find the permutation after 999 steps.

As you note, [1,5,7,6,2,3,4] is the result one iteration of A then B. Calculating that, in this form, clearly takes O(N) work. It is a little more convenient to write that as:

{
    1: 1,
    2: 5,
    3: 6,
    4: 7,
    5: 2,
    6: 4,
    7: 3
}

Again, this translation takes O(N) work.

Now let's compute a partial answer. We start with [0,0,0,0,0,0,0]' where 0` means "unknown".

First step, we find 1 -> 1 so the first cycle is just (1). Going around this cycle 999 times gives us (1) again, so we now have [1,0,0,0,0,0,0].

Second step, we find that 2 -> 5 -> 2 (note, we're just looking these up in the lookup, so each one is O(1) work) so the second cycle is (2, 5). Going around this cycle 999 steps means we get to fill in 2 values. We now have [1,5,0,0,2,0,0].

Third step, we find that 3 -> 6 -> 4 -> 7 -> 3. So the third cycle is (3, 6, 4, 7). Going around this 999 times is like walking 3 steps forward, so now we can fill in: [1,5,7,6,2,3,4].

As we go through the rest of the numbers, we find that they are all filled in. So our answer is [1,5,7,6,2,3,4].

In general, each number is part of a cycle of length j. When we find a cycle, we take O(j) work to find the cycle, and then for another O(j) work we fill in the answer of what happens to that cycle. Afterwards we will hit j-1 elements that are filled in and skip them. So we get n elements for O(n) work, for amortized O(1) work per element.

The result is O(N) work to find the final answer.

over 4 years ago · Santiago Trujillo Report

0

Here's another version of what I understand as btilly's cycle approach. Working Python code submitted to USACO:

import collections

def f(n, k, ai, aj, bi, bj):
  # A cycle necessarily has even parity as both A and B
  # must be run. We know a cycle is complete when the
  # element returns to the start and the parity is even.
  cycles = collections.defaultdict(list)

  # Get cycles
  for i in range(1, n + 1):
    j = i
    parity = 0
    first_cycle = 1

    while first_cycle or j != i or parity:
      # A
      if not parity:
        j = j if (j < ai or j > aj) else aj - j + ai
      # B
      else:
        j = j if (j < bi or j > bj) else bj - j + bi
        first_cycle = 0

      if parity: 
        cycles[i].append(j)

      parity ^= 1

  new_list = [None] * n

  for i in range(1, n + 1):
    idx = cycles[i][(k - 1) % len(cycles[i])]
    new_list[idx-1] = str(i)

  file = open("swap.out", "w")
  file.write("\n".join(new_list))
  file.close()


file = open("swap.in","r")
data = file.readlines()

[n, k] = map(int, data[0].split())
[ai, aj] = map(int, data[1].split())
[bi, bj] = map(int, data[2].split())
  
f(n, k, ai, aj, bi, bj)


"""
ai = 2
aj = 5
bi = 3
bj = 7

n = 7
k = 2
"""

"""
1
2
4
3
5
7
6
"""

enter image description here

over 4 years ago · Santiago Trujillo Report

0

Another implementation of @btilly's algorithm with my understanding, written before I realized @MichaelSzczesny has posted his:

n, k, a1, a2, b1, b2 = map(int, '''100 1000000000
1 94
2 98'''.split())
*positions, = *mapped, = range(n)
for bound in map(slice, (a1 - 1, b1 - 1), (a2, b2)):
    mapped[bound] = reversed(mapped[bound])
mapping = dict(zip(mapped, positions))
cycles = []
pool = set(positions)
while pool:
    current = pool.pop()
    cycle = [current]
    while True:
        current = mapping[current]
        if current == cycle[0]:
            break
        pool.remove(current)
        cycle.append(current)
    cycles.append(cycle)
result = [0] * n
for cycle in cycles:
    for i, position in enumerate(cycle):
        result[cycle[(k + i) % len(cycle)]] = position + 1
print(*result)

This outputs:

71 2 3 74 10 76 7 8 79 15 81 12 13 84 20 86 17 18 89 25 91 22 23 94 30 96 27 28 1 35 4 32 33 6 40 9 37 38 11 45 14 42 43 16 50 19 47 48 21 55 24 52 53 26 60 29 57 58 31 65 34 62 63 36 70 39 67 68 41 75 44 72 73 46 80 49 77 78 51 85 54 82 83 56 90 59 87 88 61 95 64 92 93 66 5 69 97 98 99 100

Timing statistics show that this is somewhat faster than @MichaelSzczesny's implementation: https://replit.com/@blhsing/EquatorialRemorsefulMath

over 4 years ago · Santiago Trujillo Report
Answer question
Find remote jobs

Discover the new way to find a job!

Top jobs
Top job categories
Business
Post vacancy Pricing Sales
Legal
Terms and conditions Privacy policy
© 2026 PeakU Inc. All Rights Reserved.
Andres GPT
Show me some job opportunities
There's an error!