Rating:

[Link to original writeup](https://wrecktheline.com/writeups/m0lecon-2021/#left_or_right_writeup/)

# Left or right? (19 solves, 217 points)
by y011d4

```
Just follow your instinct. Are you going left or right?

nc challs.m0lecon.it 5886

Authors: Drago_1729, matpro, mr96
```

This challenge is algorithmic task.

```
Hello hacker! This challenge will test your programming skills.
I will give you some strings made up only by L and R. You start at a point O, L means moving to the left by 1 unit, R means moving to the right by 1 unit.
Your objective is to concatenate ALL these strings such that the leftmost point that you reach during your path is as near as possible to O.
Let's call this point P. What is the distance between O and P?

The first line of the input contains a number N.
The following N lines contain a string made up only of the characters L and R.
Your answer should be a non-negative integer.
In every testcase 0<N<=150 and the sum of the lengths of the given strings is at most 100000.
You must answer to 200 testcases to get the flag! Time limit is one second for each test.
```

Examples are:

```
4
LRLRLLRRRLLRLLRRRRLR
LRLRRLRLLRLLLRR
LRRLRLLLLLLLLLLRLRRLLLLRRLLLRRRL
LLLRLRLLR
```

First of all, we need to hold not all "LR" but:
- the leftmost place from the current position (call it `lr_diff_max`)
- the final place from the current position (call it `last_pos`)

for each string, where the left side is positive.
For example above:

```
lr_diff_max: 2, 3, 12, 3
last_pos: -2, 1, 10, 1
```

Note that `lr_diff_max >= last_pos`.

In the following, we consider the case `last_pos <= 0` and the other case separately.

#### `last_pos` <= 0
In this case, the last position will be on the right or the same. This is higher priority than the other case.

For each choice, we should pick the case of `lr_diff_max` <= `ans` (`ans` means the current answer for each task) because it won't affect the answer. If there are no such cases, choose the smallest `lr_diff_max` case in order to minimize the `ans`

#### `last_pos` > 0
After the all `last_pos` <= 0 cases are chosen, we go to this case.
Considering the fact that the final position (call it `all_last_pos`) after all are selected doesn't change, we should pick the case of maximum `lr_diff_max - last_pos` in each choice, because naively the final `ans` equals to the final `lr_diff_max - last_pos + all_last_pos`.

The all script is as follow:

```python
import re
from hashlib import sha256
from itertools import product

import numpy as np
from pwn import *

_r = remote("challs.m0lecon.it", 5886)
ret = _r.recvline().strip().decode()
prefix, suffix = re.findall(
r"Give me a string starting with (.+) such that its sha256sum ends in (.+).", ret
)[0]

for i_list in product(list(range(32, 128)), repeat=4):
c_list = list(map(chr, i_list))
tmp = prefix + "".join(c_list)
tmp_hash = sha256(tmp.encode()).hexdigest()
if tmp_hash[-len(suffix):] == suffix:
print("found!")
_r.sendline(tmp)
break

_r.sendlineafter("Time limit is one second for each test.\n", "")

for epoch in range(200):
print(epoch)
N = int(_r.recvline().strip())
steps_list = []
for _ in range(N):
steps_list.append(_r.recvline().strip().decode())
lr_diff_max_list_0 = []
lr_diff_max_list_1 = []
last_pos_list_0 = []
last_pos_list_1 = []
for steps in steps_list:
lr_diff_max = 0
tmp = 0
for step in steps:
if step == "L":
tmp += 1
else:
tmp -= 1
if tmp > lr_diff_max:
lr_diff_max = tmp
if tmp <= 0:
lr_diff_max_list_0.append(lr_diff_max)
last_pos_list_0.append(tmp)
else:
lr_diff_max_list_1.append(lr_diff_max)
last_pos_list_1.append(tmp)

lr_diff_max_list_0 = np.array(lr_diff_max_list_0)
lr_diff_max_list_1 = np.array(lr_diff_max_list_1)
last_pos_list_0 = np.array(last_pos_list_0)
last_pos_list_1 = np.array(last_pos_list_1)

ans = 0
# case0: last_pos <= 0
n_0 = len(lr_diff_max_list_0)
used = [False] * n_0
current_pos = 0

for _ in range(n_0):
found = None
for i in range(n_0):
if not used[i] and lr_diff_max_list_0[i] <= current_pos:
found = i
break
if found is None:
pos_min = 10 ** 10
for i in range(n_0):
if not used[i] and lr_diff_max_list_0[i] <= pos_min:
pos_min = lr_diff_max_list_0[i]
found = i
assert found is not None
used[found] = True
ans = max(ans, current_pos + lr_diff_max_list_0[found])
current_pos += last_pos_list_0[found]

# case1: last_pos > 0
argidx = np.argsort(lr_diff_max_list_1 - last_pos_list_1)[::-1]
for idx in argidx:
ans = max(ans, current_pos + lr_diff_max_list_1[idx])
current_pos += last_pos_list_1[idx]
_r.sendline(str(ans))
if "Yay" not in _r.recvline().strip().decode():
break
print(_r.recvall())
```

`ptm{45_r16h7_45_p0551bl3}`

Original writeup (https://wrecktheline.com/writeups/m0lecon-2021/#left_or_right_writeup).