-2

Suppose Alice & Bob have to write from page 1 to page 200. According to this simple division, Alice will write 1, 2, 3... until 100. Bob will write 101, 102, 103... to 200. Bob will have to write a lot more digits than Alice! Let's say Alice & Bob are counters or markers for numbering, so how we can fairly split up this numbering task?

Considering two integers, start & end, for the starting and ending page numbers (inclusive) defining the range of pages that needs handwritten numbering.

  • A page number has to be written by either Alice or Bob. They cannot jointly write one number.

  • Page numbers are all decimal integers count from 1. The missing number of pages can start from page 1 or anywhere in the middle of the notes.

Input: There are multiple tests in each test case.

Line 1: Integer N, the number of tests to follow.

Following N lines: Each line has two integers, st ed, for the starting and ending page numbers (inclusive) defining the range of pages that needs handwritten numbering.

#Input examples
4       #N=4 it means 4 following lines
1 200
8 10
9 10
8 11

import sys
import math

n = int(input()) #1 ≤ N ≤ 200
for i in range(n): #1 ≤ start < end ≤ 10,000,000
    start, end = [int(j) for j in input().split()]

Output:

N Lines: For the test in the input, It should be written the corresponding last page number that should be responsible by Alice to write the needed page number on.

#Output examples
118
9
9
9

I was trying to get inspired by this post on fair casting dice unsuccessfully. I also was wondering the solution is far from Checking number of elements for Counter .

Mario
  • 1,631
  • 2
  • 21
  • 51
  • 1
    Related questions: https://stackoverflow.com/questions/2059680/how-to-count-each-digit-in-a-range-of-integers https://stackoverflow.com/questions/1397737/how-to-get-the-digits-of-a-number-without-converting-it-to-a-string-char-array – Stef Sep 09 '20 at 10:43

3 Answers3

0

First thing to note is this cannot be done, consider the sequence 99, 100. You cannot split this up fairly. In saying that you can get pretty close +- 1 digit, this assumes you always start counting from 1.

start = 1
end = 200

bobs_numbers = []
alices_numbers = []
count = 0

for i in range(end, start - 1, -1):
    if count > 0:
        bobs_numbers.append(i)
        count -= len(str(i))
    else:
        alices_numbers.append(i)
        count += len(str(i))

print(bobs_numbers, alices_numbers, count)
Mitch
  • 3,342
  • 2
  • 21
  • 31
  • Awesome algorithm! Your answer would benefit for an explanation why it helps to count backwards and why that guarantees a solution at +-1 digit. – Stef Sep 08 '20 at 21:18
  • 2
    You can't guarantee ±1 digit (unless the range is guaranteed to have even length). For example, with the range 998-1000, the best division is 6 vs. 4, and in general if you have [10^k-2, 10^k] then the best division is 2k:k+1, which can be arbitrarily close to 2:1 as k increases. – rici Sep 08 '20 at 21:30
  • Very fair point, although in the case of [1, 200] you can guarantee +-0 :p – Stef Sep 08 '20 at 21:36
  • @stef: Sure. But although the question is worded in a somewhat confusing manner, it seems to me that it wants to allow the start and the end of the range to be arbitrary positive numbers. – rici Sep 08 '20 at 21:48
  • @Mitchel Paulin Thanks for your cool input but it doesn't meet the test cases. I had to update the question by including **input & output examples** so that you could update your answer to meet the test cases. – Mario Sep 09 '20 at 05:14
  • @rici thanks for insightful input but it's not a case here concerning [1, 200], in our case. I clarified by including input & output examples. – Mario Sep 09 '20 at 05:35
  • @mario: your example output seems to imply that Alice and Bob each write a contiguous range of numbers, in which case this answer doesn't apply. But I certainly didn't understand that from the phrasing of your question. Also, my comment says that the range of pages to be numbered does not have to be [1,200], or even start at 1. That seems to be confirmed by your examples, so I don't get ehat you mean by "but it's not a case here". Probably this is all just confusing use of language. – rici Sep 09 '20 at 06:22
  • @rici Oh I just got your point of view by that comment concerning it does not *necessarily* start from 1. Nevertheless, in my scenario, Alice & Bob are going to numbering a book and *indeed begin at 1*. I hope the way I phrased the question is not vague anymore by including input and output examples. – Mario Sep 09 '20 at 06:32
  • Dear @Mario, your original question was really cool and MitchelPaulin and I both wrote algorithms to solve it. Now you changed the question, and our algorithms no longer fulfill the question's requirements. MitchelPaulin's algorithm was extremely simple and gave the optimal solution to the original problem. It would be a bit sad if they had to delete everything they wrote just because you changed the question. Would you consider reverting to the original question, and making a new StackOverflow question? With links pointing from one to the other because they share similarities. – Stef Sep 09 '20 at 09:22
  • In particular, requiring the output to be as a range [1, m] for Alice and [m+1, n] for Bob is a huge change compared to allowing any subset for Alice and the complementary subset for Bob. – Stef Sep 09 '20 at 09:25
  • Dear @Stef I very liked MichelPaulin solution but I was wondering if you are kind enough to consider the right solution according to input & output examples by just updating the answer. I don't want to *duplicate* it by asking a similar question for *fair numbering* and keep this post unique. I'm sure that updating answers wouldn't be a big deal. The aim is to discuss and make the best out of solutions based on input & output examples which I should have mentioned at the beginning to phrase question correctly. it was my bad. – Mario Sep 09 '20 at 09:36
  • @Mitchel Paulin, I'm so interested to see what's your point of view in this regard to catch the optimum answer based on *input & output examples*. – Mario Sep 09 '20 at 10:57
0

This is an answer to the initial question. Since the question has been changed, I posted another answer for the new question.

The initial question was: Partition the set [1, 200] into two subsets such that the total number of digits in one subset is as close to possible to the total number of digits in the other subset.

Since user Mitchel Paulin already gave a straightforward iterative solution, let me give an arithmetic solution.

Counting the number of digits

First, let's count the total number of digits we want to split between Alice and Bob:

  • there are 9 numbers with 1 digit;
  • there are 90 numbers with 2 digits;
  • there are 101 numbers with 3 digits.

Total: 492 digits.

We want to give 246 digits to Alice and 246 digits to Bob.

How to most simply get 246 digit by summing up numbers with 1, 2 and 3 digits?

246 = 3 * 82.

Let's give 82 numbers with 3 digits to Bob, and all the other numbers to Alice.

Finally Bob can handle numbers [119, 200] and Alice can handle numbers [1, 118].

Generalizing to any range [1, n]

Counting the numbers of numbers with each possible number of digits should be O(log n).

Dividing by 2 to get the number of digits for Bob is O(1).

Decomposing this number using dynamic programming is linear in the maximum number of digits, i.e., O(log n) space and time (this is exactly the coin change problem).

Transforming this decomposition into a union of ranges is straightforward, and linear in the maximum number of digits, so again O(log n). Deducing the ranges for Alice by "subtracting" Bob's ranges from [1, n] is also straightforward.

Conclusion: the algorithm is O(log n) space and time, as opposed to Mitchel Paulin's O(n) algorithm. The output is also logarithmic instead of linear, since it can be written as a union of ranges, instead of a long list.

This algorithm is a bit more complex to write, but the output being in the form of ranges mean that Alice and Bob won't bother each other too much by writing adjacent pages, which they would do a lot with the simpler algorithm (which mostly alternates between giving a number to Bob and giving a number to Alice).

Stef
  • 13,242
  • 2
  • 17
  • 28
  • Thanks for your insightful point nevertheless I had to update question by including input & output examples. – Mario Sep 09 '20 at 09:13
0

Since the question has changed, this is an answer the new question.

The new question is: Given a range [a, b], find number m such that the total number of digits in range [a, m] is as close as possible to the number of digits in range [m+1, b].

Algorithm explanation

The algorithm is simple: Start with m = (a + b) / 2, count the digits, then move m to the right or to the left to adjust.

To count the total number of digits in a range [1, n], we first count the number of unit digits (which is n); then add the number of tens digits (which is n - 9; then add the number of hundreds digits (which is n - 99); etc.

To count the total number of digits in a range [a, b], we take the difference between the total number of digits in ranges [1, b] and [1, a-1].

Note that the number of digits of a given number n > 1 is given by any of the two expressions math.ceil(math.log10(n)) and len(str(n)). I used the former in the code below. If you have a phobia of logarithms, you can replace it with the latter; in which case import math is no longer needed.

Code in python

import math

def count_digits_from_1(n):
    power_of_ten = math.ceil(math.log10(n))
    total_digits = 0
    for i in range(1, power_of_ten+1):
        digits_at_pos_i = n - (10**(i-1) - 1)
        total_digits += digits_at_pos_i
    return total_digits

def count_digits(a, b):
    if a > 2:
        return count_digits_from_1(b) - count_digits_from_1(a-1)
    else:
        return count_digits_from_1(b) - (a - 1)   # assumes a >= 1

def share_digits(a, b):
    total_digits = count_digits(a, b)
    m = (a + b) // 2
    alices_digits = count_digits(a, m)
    bobs_digits = total_digits - alices_digits

    direction = 1 if alices_digits < bobs_digits else -1

    could_be_more_fair = True
    while (could_be_more_fair):
        new_m = m + direction
        diff = math.ceil(math.log10(new_m))
        new_alices_digits = alices_digits + direction * diff
        new_bobs_digits = bobs_digits - direction * diff
        if abs(alices_digits - bobs_digits) > abs(new_alices_digits - new_bobs_digits):
            alices_digits = new_alices_digits
            bobs_digits = new_bobs_digits
            m = new_m
        else:
            could_be_more_fair = False

    return ((a, m), (m+1, b))

if __name__=='__main__':
    for (a, b) in [(1, 200), (8, 10), (9, 10), (8, 11)]:
        print('{},{}  --->  '.format(a,b), end='')
        print(share_digits(a, b))

Output:

1,200  --->  ((1, 118), (119, 200))
8,10  --->  ((8, 9), (10, 10))
9,10  --->  ((9, 9), (10, 10))
8,11  --->  ((8, 10), (11, 11))

Remark: This code uses the assumption 1 <= a <= b.

Performance analysis

Function count_digits_from1 executes in O(log n); its for loop iterates over the position of the digits to count the number of unit digits, then the number of tens digits, then the number of hundreds digits, etc. There are log10(n) positions.

The question is: how many iterations will the while loop in share_digits have?

If we're lucky, the final value of m will be very close to the initial value (a+b)//2, so the number of iterations of this loop might be O(1). This remains to be proven.

If the number of iterations of this loop is too high, the algorithm could be improved by getting rid of this loop entirely, and calculating the final value of m directly. Indeed, replacing m with m+1 or m-1 changes the difference abs(alices_digits - bobs_digits) by exactly two times the number of digits of m+1 (or m-1). Therefore, the final value of m should be given approximately by:

new_m = m + direction * abs(alices_digits - bobs_digits) / (2 * math.ceil(math.log10(m)))

Stef
  • 13,242
  • 2
  • 17
  • 28
  • Thanks for the update of your solution. The only thing is your solution return output pair-wisely which is not matching with output example. – Mario Sep 09 '20 at 10:36
  • @Mario: Surely you can figure out how to format the output yourself. – Stef Sep 09 '20 at 10:38
  • Dear @Stef may I ask you kindly to reform your answer using `n = int(input())` `start` `end` regarding your final for-loop, I still can't generate/print expected I/O examples via your answer! The output of your answer due to `b` is `[118, 9, 9, 8]` which should be `[118, 9, 9, 9]`. Did you notice that? – Mario Sep 09 '20 at 11:43
  • Dear @Mario, if you experience difficulties processing the input and formatting the output, please post a question focusing on your input/output difficulties. – Stef Sep 09 '20 at 11:53
  • Are you saying [8, 9, 10, 11] should split as [8, 9] for Alice and [10, 11] for Bob? This results in Alice writing 2 digits and Bob writing 4 digits. My program suggests giving {8, 9, 10} to Alice and {11} to Bob, with result Alice writing 4 digits and Bob writing 2 digits. Notice both solutions are equally unfair : someone writes 2 digits and the other writes 4 digits. Your question does not state how to choose between two solutions which are equally unfair. However, you can replace the symbol `>` with `>=` on the `if abs(...) > abs(...)` line of my code. Observe how that affects the output. – Stef Sep 09 '20 at 11:58
  • Dear @Stef thanks for your consideration. yes, It should be [8, 9] for Alice and [10, 11] for Bob. Surprisingly, your solution return back [8, 10] for Alice and [11, 11] for Bob! it skips [9] from nowhere! I tested >= but it didn't work correctly as well. – Mario Sep 09 '20 at 12:07
  • @Mario: the solution doesn't "skip 9", it returns the interval [8, 10] for Alice, which is the set {8, 9, 10}, just like in the output for [1, 200] Alice got the interval [1, 118], which is the set {1, 2, 3, 4, 5, ..., 115, 116, 117, 118}. – Stef Sep 09 '20 at 12:09
  • Kindly, I don't see eye to eye with you, but it doesn't match with the right output as you see in the last example test (regardless of your interpretation on set) and it shows that one of the functions in your solution mess up! but thanks for your effort. – Mario Sep 09 '20 at 12:19
  • @Mario: I already explained, [8,9],[10,11] and [8,10][11,11] are **both** solutions for this example. One is giving more work to Alice and one is giving more work to Bob. I **also** explained that you can change `<` with `<=` and see how that might result in a different solution when there are more than one solution. There are two lines where you can play with `<`/`<=` or `>`/`>=`. Depending on them, the algorithm will be biased to give more work to Alice or to Bob when two solutions are possible. Choose whichever your prefer. Your question does not state how to choose. – Stef Sep 09 '20 at 12:41