-
Notifications
You must be signed in to change notification settings - Fork 36
/
Copy patheuler-0108.cpp
215 lines (186 loc) · 5.72 KB
/
euler-0108.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
// ////////////////////////////////////////////////////////
// # Title
// Diophantine reciprocals I
//
// # URL
// https://projecteuler.net/problem=108
// http://euler.stephan-brumme.com/108/
//
// # Problem
// In the following equation `x`, `y`, and `n` are positive integers.
//
// `dfrac{1}{x} + dfrac{1}{y} = dfrac{1}{n}`
//
// For `n = 4` there are exactly three distinct solutions:
//
// `dfrac{1}{5} + dfrac{1}{20} = dfrac{1}{4}`
//
// `dfrac{1}{6} + dfrac{1}{12} = dfrac{1}{4}`
//
// `dfrac{1}{8} + dfrac{1}{8} = dfrac{1}{4}`
//
// What is the least value of n for which the number of distinct solutions exceeds one-thousand?
//
// NOTE: This problem is an easier version of Problem 110; it is strongly advised that you solve this one first.
//
// # Solved by
// Stephan Brumme
// May 2017
//
// # Algorithm
// It's safe to assume `x <= y`. Moreover, `x > n` because `frac{1}{x} < frac{1}{n}`. That means `y > n`, too.
//
// There must be some `a` and `b` such that `x = n + a` and `y = n + b`.
// The original equation becomes:
//
// `dfrac{1}{n + a} + dfrac{1}{n + b} = dfrac{1}{n}`
//
// Multiply both sides by `n`:
//
// `dfrac{n}{n + a} + dfrac{n}{n + b} = dfrac{n}{n} = 1`
//
// Multiply by `n+a`:
//
// `dfrac{n(n+a)}{n + a} + dfrac{n(n+a)}{n + b} = n + a`
//
// `n + dfrac{n(n+a)}{n + b} = n + a`
//
// And the same for `n+b`:
//
// `n(n+b) + dfrac{n(n+a)(n+b)}{n + b} = (n + a)(n + b)`
//
// `n(n+b) + n(n+a) = (n + a)(n + b)`
//
// Simplify:
// `n^2 + nb + n^2 + na = n^2 + na + nb + ab`
// `n^2 = ab`
//
// In the example where `n=4` you can compute `x = n + a` ==> `5 = 4 + a` ==> `a = 1` and because of `y = n + b` ==> `b = 16`.
// Indeed, `ab = 1 * 16 = 16 = 4^2 = n^2`.
// (And for the other two solutions: `ab = (6-4) * (12-4) = 16 = n^2` and `ab = (8-4) * (8-4) = 16 = n^2`).
//
// What does it mean ? Well, finding all `ab = n^2` produces all solutions.
// The number of solutions is the number of divisors of `n^2`.
//
// My first attempt was based on brute force and is at the end of the code (it's not used anymore).
// A smarter approach is to perform a prime factorization.
//
// A high number of divisors means that prime factors must be small.
// To speed up the program I abort when a prime factor > 100 is left. This is kind of cheating ... and roughly 100x faster.
//
// # Hackerrank
// I didn't notice that prime factorization of `n^2` can be reduced to a slightly modified prime factorization of `n`.
// Thanks to http://www.mathblog.dk/project-euler-108-diophantine-equation/ where I found that trick (see ''numSquareDivisors'')
// The code becomes much faster but I kept my old code ''numDivisors'' for the original problem.
//
// However, I still time-out on two thirds of the test cases (my old code timed out in 80% of all cases).
#include <iostream>
//#define ORIGINAL
// count divisors
unsigned long long numDivisors(unsigned long long n)
{
unsigned int result = 1;
auto reduce = n;
// trial division by all prime numbers
// => I didn't precompute a sieve, therefore divide by 2 and all odd numbers
for (unsigned long long divisor = 2; divisor <= reduce; divisor++)
{
// 2 is the only even prime number
if (divisor % 2 == 0 && divisor > 2)
divisor++;
if (divisor > 100) // WARNING: unsafe speed optimization !
break; // returns correct values for original problem but fails for some Hackerrank test cases
unsigned int exponent = 0;
while (reduce % divisor == 0)
{
exponent++;
reduce /= divisor;
}
result *= exponent + 1;
}
return result;
}
// count divisors of n^2, note: parameter is n, not n^2 (this is different from my old code in numDivisors)
unsigned long long numSquareDivisors(unsigned long long n)
{
unsigned int result = 1;
auto reduce = n;
// trial division by all prime numbers
// => I didn't precompute a sieve, therefore divide by 2 and all odd numbers
for (unsigned long long divisor = 2; divisor <= reduce; divisor++)
{
// 2 is the only even prime number
if (divisor % 2 == 0 && divisor > 2)
divisor++;
unsigned int exponent = 0;
while (reduce % divisor == 0)
{
exponent++;
reduce /= divisor;
}
result *= 2*exponent + 1; // changed vs. my code: times 2
}
return result;
}
int main()
{
#ifdef ORIGINAL
unsigned long long n = 1;
unsigned long long threshold = 1000;
while (true)
{
auto divisors = numDivisors(n * n);
// a and b are interchangeable therefore only half of the solutions are "unique"
auto half = (divisors + 1) / 2; // plus 1 because n^2 is obviously a square number
if (half >= threshold)
{
std::cout << n << std::endl;
break;
}
// check next square number
n++;
}
#else
unsigned int tests;
std::cin >> tests;
while (tests--)
{
// find the number of solutions
unsigned long long n;
std::cin >> n;
auto divisors = numSquareDivisors(n);
// a and b are interchangeable therefore only half of the solutions are "unique"
auto half = (divisors + 1) / 2; // plus 1 because n^2 is obviously a square number
std::cout << half << std::endl;
}
#endif
return 0;
}
// my initial brute force solution
int bruteForce(unsigned int threshold)
{
unsigned long long n = 4;
while (true)
{
unsigned int solutions = 0;
// try all values between n+1 ... 2n
for (unsigned long long x = n + 1; x <= 2*n; x++)
{
// the same as 1/x + 1/y = 1/n
auto y = n*x / (x - n);
// integer arithmetic might produce a wrong result, re-compute the formula backwards
if (y * (x - n) != n*x)
continue;
// exhausted ?
if (y < x)
break;
// valid solution
solutions++;
}
if (solutions > threshold)
break;
n++;
}
std::cout << n << std::endl;
return 0;
}