It is currently Thu Apr 18, 2024 6:30 pm

All times are UTC - 8 hours [ DST ]




Post new topic Reply to topic  [ 9 posts ] 
Author Message
Offline
 Post subject: L19 Programming Problem Championship: Round 6 (Math)
Post #1 Posted: Thu Jun 08, 2017 2:34 pm 
Gosei
User avatar

Posts: 1848
Location: Bellevue, WA
Liked others: 90
Was liked: 837
Rank: AGA 5d
KGS: Capsule 4d
Tygem: 치킨까스 5d
Leaderboard

  1. bernds - 24
  2. ugoertz - 16
  3. Solomon, gamesorry - 15
  4. dfan - 9
  5. peti29 - 6
  6. jeromie, quantumf - 5
  7. Kirby - 3

Contest Link: https://open.kattis.com/contests/efr2j9

Start Time: 2017-06-09 17:00:00 UTC (Fri June 9 2017 10am PST, 19:00 CEST)
Duration: 60 hours
Problems: 6
Theme: Math

Past Rounds:


This post by Solomon was liked by: gamesorry
Top
 Profile  
 
Offline
 Post subject: Re: L19 Programming Problem Championship: Round 6 (Math)
Post #2 Posted: Thu Jun 08, 2017 2:40 pm 
Gosei
User avatar

Posts: 1848
Location: Bellevue, WA
Liked others: 90
Was liked: 837
Rank: AGA 5d
KGS: Capsule 4d
Tygem: 치킨까스 5d
Also, this round might be good for people who don't like coding so much but enjoy math, because I have a feeling (i.e., I don't really know but it's just my hunch) that the amount of code needed to be written for these questions will be short.

Top
 Profile  
 
Offline
 Post subject: Re: L19 Programming Problem Championship: Round 6 (Math)
Post #3 Posted: Sun Jun 11, 2017 10:13 pm 
Gosei
User avatar

Posts: 1848
Location: Bellevue, WA
Liked others: 90
Was liked: 837
Rank: AGA 5d
KGS: Capsule 4d
Tygem: 치킨까스 5d
Spent way too much time thinking about and trying to figure out Three Digits (C) :(. A and D weren't too bad, and I went with an approximation formula for B so it really only feels like I got 2. Even though both B and C both centralized on factorials, I just could not figure out a way to work around C without actually computing the factorial. Failing to solve C and E (unfortunately never got around to F), I think this only made me realize how weak my grasp on number theory is (despite having a degree in mathematics, my classes heavily outweighed probability theory and statistics and I had never taken courses in number theory or abstract algebra, which I do regret).

Anyways, I would love to hear from ugoertz, bernds, or gamesorry how they tackled C and E (especially C) and I hope it's not too complicated!

Top
 Profile  
 
Offline
 Post subject: Re: L19 Programming Problem Championship: Round 6 (Math)
Post #4 Posted: Sun Jun 11, 2017 10:49 pm 
Lives with ko

Posts: 149
Liked others: 276
Was liked: 49
Rank: 3d
KGS: 3d
DGS: 3d
OGS: 3d
For both C and E, I submitted the Python3 codes first but got TLEs, even if I hard-coded a prime number table in E. After converting them into C++ they got accepted within the time limit with quite large margin. For D Python worked nicely for the large number though. So I guess next time I need to quickly figure out whether the time limit would be an important factor before choosing the language. (ugoertz got accepted in Python3 for E, and I'd like to know how to achieve that :o )

C: there's a famous problem of counting the number of trailing zeroes in n! - the answer is to count how many 5s are in the factors of 1..n. Therefore, in order to remove them, we just need to avoid multiplying these 5-factors and the same number of 2-factors, so that the last digit of the result would always be non-zero. After that, we just need to mod 1000 during multiplication to keep the last 3 digits.

C solution, 0.07 s
Code:
#include <stdio.h>

int main() {
   int n;
   scanf("%d", &n);
   int m = n;
   int c = 0;
   while (m >= 5) {
      c += m/5;
      m /= 5;
   }
   //printf("c: %d\n", c);
   int p = 1;
   for (int i = 2 ; i <= n ; i++) {
      int k = i;
      while (k % 5 == 0) k /= 5;
      while (c && ((k & 1) == 0)) { c--; k >>= 1; }
      p = (p * (k % 1000)) % 1000;
      //printf("%d %d %d\n", i, k, p);
   }

   if (n < 7) printf("%d\n", p);
   else if (p >= 100) printf("%d\n", p);
   else if (p >= 10) printf("0%d\n", p);
   else printf("00%d\n", p);
   return 0;
}

Almost same code in Python, TLE
Code:
n = int(input())

c = 0
m = n
while (m >= 5):
   c += m // 5
   m //= 5

# print(c)
p = 1

for i in range(2, n+1):
   k = i
   while (k % 5 == 0):
      k //= 5
   while (c > 0 and k % 2 == 0):
      c -= 1
      k //= 2
   p = (p * k) % 1000
   # print('d', i, k, p)

if (n < 7):
   print(p)
else:
   if (p >= 100):
      print(p)
   elif (p >= 10):
      print('0%d' % p)
   else:
      print('00%d' % p)


E: If the last digit of n in base k is 3, then n mod k = 3 (k > 3), or (n-3) mod k = 0 - more importantly, this property can hold in the other direction, i.e., if (n-3) mod k = 0 (k > 3), then the last digit of n in base k must be 3. Therefore, (n-3) must be divisible by the base k and we only need to find out the minimum factor of (n-3) that is larger than 3. There are quite a few corner cases and hand-checking the examples of 1..20 helps a lot.

C++ solution in 0.03 s
Code:
#include <cstdio>
#include <cmath>
#include <vector>
using std::vector;

int p[4792] = {2, 3, 5, 7, ... //////omitted in the post

int main()
{
   int n;
   while (true) {
      scanf("%d", &n);
      if (n == 0) break;
      else if (n < 3 || n == 4) {
         printf("No such base\n");
         continue;
      }
      else if (n == 3) {
         printf("4\n");
         continue;
      }

      int x = n - 3;
      vector<int> a;
      int k = 0;
      int r = sqrt(x);
      while (x > 1) {
         bool changed = false;
         while (x % p[k] == 0) {
            a.push_back(p[k]);
            x /= p[k];
            changed = true;
         }
         if (a.size() > 0 and a[a.size() - 1] > 3) break;
         if (changed) r = sqrt(x);
         k++;
         if (k >= 4792 || p[k] > r) break;
      }

      if (x > 1) a.push_back(x);
      if (a[0] > 3) printf("%d\n", a[0]);
      else if (a.size() == 1) printf("No such base\n");
      else {
         int ans = a[0] * a[1];
         for (int i = 0 ; i < a.size() ; i++)
            if (a[i] > 3 && a[i] < ans) ans = a[i];
         printf("%d\n", ans);
      }
   }

   return 0;
}

Python code with the same logic getting TLE:
Code:
import math
"""
m = math.floor(math.sqrt(2**31)) + 1
b = [0 for i in range(m)]
p = []
for i in range(2, m):
   if (b[i] == 0):
      p.append(i)
      k = i + i
      while k < m:
         b[k] = 1
         k += i

print(p)
print(m)
"""
p = [2, 3, 5, 7, ... # 4792 prime numbers, omitted in forum post
# print(len(p))
while (True):
   n = int(input())
   if n==0:
      break
   elif n<3 or n==4:
      print("No such base")
      continue
   elif n==3:
      print(4)
      continue

   x = n-3
   
   a = []
   k = 0
   r = math.floor(math.sqrt(x))

   while (x > 1):
      changed = False
      while (x % p[k] == 0):
         a.append(p[k])
         x //= p[k]
         changed = True
         
      
      if (len(a) > 0 and a[len(a)-1] > 3):
         break
      if changed:
         r = math.floor(math.sqrt(x))
      k += 1
      if (k >= len(p) or p[k] > r):
         break

   if (x > 1):
      a.append(x)

   if (a[0] > 3):
      print(a[0])
   elif (len(a) == 1):
      print("No such base")
   else:
      # len(a) >= 2
      ans = a[0] * a[1]
      for i in range(len(a)):
         if a[i] > 3 and a[i] < ans:
            ans = a[i]
      print(ans)


I'm wondering if there's an elegant solution for A. I worked on it for an hour trying to write down a dp solution but couldn't figure it out completely - there seem to be many cases to be analyzed.

Top
 Profile  
 
Offline
 Post subject: Re: L19 Programming Problem Championship: Round 6 (Math)
Post #5 Posted: Mon Jun 12, 2017 3:34 am 
Dies in gote
User avatar

Posts: 63
Liked others: 0
Was liked: 40
Thanks, Solomon, for setting up the contest! Here are some thoughts on my solutions:

A. Taking a difference in the final step, it is enough to compute the number of zeros needed to write down all numbers from 0 to n. To do this, I computed how often the i-th digit is a 0, for all i. This is quite easy: Say n = 12345, then for the 5th digit we get 1235, for the 4th digit we get 123 * 10, for the 3rd digit 12 * 100, for the second digit 1 * 1000. (If a digit is 0, the formula changes slightly.)

B. I used (the logarithm of) Stirling's formula.

C. Up to trailing zeros, we want to compute modulo 1000, so we will first discard all powers of 2 and 5, and fix that in the end. Now for every n > 0, the product of all those numbers in {n, n+1, n+2, ..., n+999} which are coprime to 2 and 5, is independent of n, and is easily checked to be equal to 1. (The independence of n has nothing to do with computing modulo 1000, of course; the value of the product is not always 1, however: e.g., doing this modulo 10, we get -1.) This means that splitting the factorial into chunks of 1000 consecutive numbers, we can ignore all those coprime to 2 and 5.

As gamesorry pointed out, all powers of 5 go into trailing zeros; so we just compute what this power is (using Legendre's formula). The same power of 2 is then also used up for the trailing zeros. The remaining power of 2 must be multiplied with what we had from the first step.

(Finally, to speed up things, I precomputed the values of the first step for multiples of 1,000,000. Then for large numbers one can start off from the greatest multiple of 1000000 which is smaller than the number in question.


D. The problem is easily translated into the problem of finding the binary expression of n-1.

E. Up to some border cases, we need to find the smallest prime factor of n-3. (The border cases are that for 1, 2, 4, 5, 6 there is no solution, and we really want to find the smallest divisor of n within [4, 5, 6, 7, 9, 11, 13, ...] (continue with primes > 13). Finally, we do not want to compute all primes up to 2**31; if no divisor in the above list <= sqrt(n) was found, then n is a prime (or twice a prime, or three times a prime), and we return n (or n//2, n//3, resp.).

@gamesorry: I think this is exactly what you said. If I understand your code correctly, you go on to find all prime divisors though, which is probably what takes too long.

F. I have "sort of" a solution but it clearly does not meet the time requirements.

You can look at the code here.

Best, Ulrich

_________________
u-go.net


This post by ugoertz was liked by: Solomon
Top
 Profile  
 
Offline
 Post subject: Re: L19 Programming Problem Championship: Round 6 (Math)
Post #6 Posted: Mon Jun 12, 2017 3:57 am 
Lives with ko

Posts: 259
Liked others: 46
Was liked: 116
Rank: 2d
Congratulations to Ulrich!

I don't have too much to add to the already posted explanations. Problem A rather reminded me of the digit sum problem from an earlier round.
ugoertz wrote:
A. Taking a difference in the final step, it is enough to compute the number of zeros needed to write down all numbers from 0 to n. To do this, I computed how often the i-th digit is a 0, for all i. This is quite easy: Say n = 12345, then for the 5th digit we get 1235, for the 4th digit we get 123 * 10, for the 3rd digit 12 * 100, for the second digit 1 * 1000. (If a digit is 0, the formula changes slightly.)
I'm guessing 1235 instead of 1234 for the fifth because zero has to be counted? I had a slightly different approach: count the number of zeros printed for numbers 0..10^n-1, with or without leading zeros, with a bit of recursion.
Quote:
B. I used (the logarithm of) Stirling's formula.
Is that what everyone did? I didn't know a good way to approximate the factorial and didn't want to just google the answer. So I thought of a solution that would have involved extracting 2s and 5s again, computing subresults that fit within a 64-bit integer, then having a series of widening multiplies discarding the low parts at every step, and organizing the calculation in a binary tree so as to minimize the rounding errors... then I looked at the column of green and decided that can't be what everyone is doing.
Quote:
As gamesorry pointed out, all powers of 5 go into trailing zeros; so we just compute what this power is (using Legendre's formula).
Huh. I'm pretty sure I didn't use that. I guess I'll need to go look at your code to see if that would have helped any; the problem seemed quite simple without it.
Quote:
E. Up to some border cases, we need to find the smallest prime factor of n-3. (The border cases are that for 1, 2, 4, 5, 6 there is no solution, and we really want to find the smallest divisor of n within [4, 5, 6, 7, 9, 11, 13, ...] (continue with primes > 13). Finally, we do not want to compute all primes up to 2**31; if no divisor in the above list <= sqrt(n) was found, then n is a prime (or twice a prime, or three times a prime), and we return n (or n//2, n//3, resp.).
Yeah. I kind of knew all or most of this, but I screwed up the last step because for a while I didn't realize I'd have prime divisors above sqrt(n) if I skipped 2 and 3. I actually realized what the problem was after I'd gone to bed on Sunday.

Top
 Profile  
 
Offline
 Post subject: Re: L19 Programming Problem Championship: Round 6 (Math)
Post #7 Posted: Mon Jun 12, 2017 6:08 am 
Lives with ko

Posts: 125
Liked others: 13
Was liked: 12
Rank: KGS 5 kyu
As always, thx for the round, Solomon!

B) I googled up a formula that had PI, E and lots of log10 in it, had no idea how it works, but apparently everyone else used the same here, so :salute:

D) I really enjoyed D. That's my level of math (shame or not). First I realized that in base 3 those sets consist of elements like 10, 100, 101, 1101, etc. And then I realized that if we sort those we get base 2 numbers in order (plus the empty set at the first place).

Top
 Profile  
 
Offline
 Post subject: Re: L19 Programming Problem Championship: Round 6 (Math)
Post #8 Posted: Mon Jun 12, 2017 8:00 am 
Dies in gote
User avatar

Posts: 63
Liked others: 0
Was liked: 40
bernds wrote:
ugoertz wrote:
A. Taking a difference in the final step, it is enough to compute the number of zeros needed to write down all numbers from 0 to n. To do this, I computed how often the i-th digit is a 0, for all i. This is quite easy: Say n = 12345, then for the 5th digit we get 1235, for the 4th digit we get 123 * 10, for the 3rd digit 12 * 100, for the second digit 1 * 1000. (If a digit is 0, the formula changes slightly.)
I'm guessing 1235 instead of 1234 for the fifth because zero has to be counted?


Yes, that's right.

Quote:
Quote:
As gamesorry pointed out, all powers of 5 go into trailing zeros; so we just compute what this power is (using Legendre's formula).
Huh. I'm pretty sure I didn't use that. I guess I'll need to go look at your code to see if that would have helped any; the problem seemed quite simple without it.

It's probably nearly as good to keep track of the 5's and 2's more directly. But Legendre's formula is quite nice (and not hard to derive and to apply), so it seemed a good opportunity to use it. I suppose the remark about omitting factors coprime to 2 and 5 in complete chunks of one thousand numbers gives only a marginal speed-up and can be ignored (for Python code maybe at the cost of precomputing a couple more values).

Best, Ulrich

_________________
u-go.net

Top
 Profile  
 
Offline
 Post subject: Re: L19 Programming Problem Championship: Round 6 (Math)
Post #9 Posted: Mon Jun 12, 2017 8:03 am 
Gosei
User avatar

Posts: 1848
Location: Bellevue, WA
Liked others: 90
Was liked: 837
Rank: AGA 5d
KGS: Capsule 4d
Tygem: 치킨까스 5d
bernds wrote:
Quote:
B. I used (the logarithm of) Stirling's formula.
Is that what everyone did? I didn't know a good way to approximate the factorial and didn't want to just google the answer. So I thought of a solution that would have involved extracting 2s and 5s again, computing subresults that fit within a 64-bit integer, then having a series of widening multiplies discarding the low parts at every step, and organizing the calculation in a binary tree so as to minimize the rounding errors... then I looked at the column of green and decided that can't be what everyone is doing.
My first attempt was to just calculate the sum of log(i) for i = 2 to n and take the floor of that, but I hit TLE (my second attempt was rewriting it in C++ only to hit TLE again). I was already aware of Stirling's formula so I just took the easy route, but afterwards realized I could have just memoized everything up to 1e6 and that could have worked as well:
Code:
from math import log, ceil

MAX_N = 1000005

def main():
    memo = [1] * MAX_N
    val = sum([log(i, 10) for i in range(1, 5)])
    for i in range(4, MAX_N):
        memo[i] = ceil(val)
        val += log(i + 1, 10)

    while True:
        try:
            print(memo[int(input())])
        except EOFError:
            break

if __name__ == '__main__':
    main()

Top
 Profile  
 
Display posts from previous:  Sort by  
Post new topic Reply to topic  [ 9 posts ] 

All times are UTC - 8 hours [ DST ]


Who is online

Users browsing this forum: No registered users and 1 guest


You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum
You cannot post attachments in this forum

Search for:
Jump to:  
Powered by phpBB © 2000, 2002, 2005, 2007 phpBB Group