simplify dirichlet convolution approach
This commit is contained in:
@@ -130,137 +130,51 @@
|
||||
"The [Dirichlet convolution](https://en.wikipedia.org/wiki/Dirichlet_convolution) of two [arithmetic functions](https://en.wikipedia.org/wiki/Arithmetic_function) $f$ and $g$ is\n",
|
||||
"$$(f * g)(n) = \\sum_{xy=n} f(x) g(y)$$\n",
|
||||
"\n",
|
||||
"Several important mathematical functions have some representation as a Dirichlet convolution - see the above page for a list. An important one for our purposes is $\\phi = \\mathrm{Id} * \\mu$, where $\\mathrm{Id(n) = n}$ is the [identity function](https://en.wikipedia.org/wiki/Identity_function) and $\\mu$ is the [Möbius function](https://en.wikipedia.org/wiki/M%C3%B6bius_function) - if you've never heard of the latter, don't worry about it yet.\n",
|
||||
"Several important mathematical functions have some representation as a Dirichlet convolution - see the above page for a list. An important one for our purposes is $\\phi * 1 = \\mathrm{Id}$, where $1$ is just the constant function $1(n) = 1$ and $\\mathrm{Id}(n) = n$ is the [identity function](https://en.wikipedia.org/wiki/Identity_function) (note that this convolution identity is the same as Gauss's identity presented above).\n",
|
||||
"\n",
|
||||
"## Dirichlet's hyperbola method\n",
|
||||
"By applying the above Dirichlet convolution to\n",
|
||||
"$$\\Phi(n) = \\sum_{k=1}^n \\phi(n)$$\n",
|
||||
"we can derive an equivalent summation:\n",
|
||||
"$$\\Phi(n) = \\sum_{k=1}^n \\sum_{xy=k} x \\mu(y)$$\n",
|
||||
"As mentioned above, it's well-known that\n",
|
||||
"$$\\sum_{k=1}^n k = \\frac{n(n+1)}{2}$$\n",
|
||||
"We can go further by applying the Dirichlet convolution above to obtain\n",
|
||||
"$$\\frac{n(n+1)}{2} = \\sum_{k=1}^n k = \\sum_{k=1}^n \\sum_{xy=k} \\phi(x)$$\n",
|
||||
"\n",
|
||||
"We're going to transform this summation by applying \n",
|
||||
"[Dirichlet's hyperbola method](https://en.wikipedia.org/wiki/Dirichlet_hyperbola_method). Let $1 < a < n$, and let $b = n / a$. Then\n",
|
||||
"$$\\Phi(n) = \\sum_{x=1}^a \\sum_{y=1}^{n/x} x \\mu(y) + \\sum_{y=1}^b \\sum_{x=1}^{n/y} x \\mu(y) - \\sum_{x=1}^a \\sum_{y=1}^b x \\mu(y)$$\n",
|
||||
"$$\\frac{n(n+1)}{2} = \\sum_{x=1}^a \\sum_{y=1}^{n/x} \\phi(x) + \\sum_{y=1}^b \\sum_{x=1}^{n/y} \\phi(x) - \\sum_{x=1}^a \\sum_{y=1}^b \\phi(x)$$\n",
|
||||
"\n",
|
||||
"Then with some algebra, we can transform this into\n",
|
||||
"$$\\Phi(n) = \\sum_{x=1}^a x M\\left(\\left\\lfloor \\frac{n}{x}\\right\\rfloor\\right) + \\sum_{y=1}^b \\mu(y) \\frac{\\left\\lfloor \\frac{n}{y} \\right\\rfloor \\left(\\left\\lfloor \\frac{n}{y}\\right\\rfloor + 1\\right)}{2} - M(\\lfloor b \\rfloor) \\frac{\\lfloor a \\rfloor(\\lfloor a\\rfloor +1)}{2}$$\n",
|
||||
"where $M(n)$ is the [Mertens function](https://en.wikipedia.org/wiki/Mertens_function), which is just the partial sums of the Möbius function:\n",
|
||||
"$$M(n) = \\sum_{k=1}^n \\mu(k)$$\n",
|
||||
"$$\\frac{n(n+1)}{2} = \\sum_{x=1}^a \\phi(x)\\left\\lfloor\\frac{n}{x}\\right\\rfloor + \\sum_{y=1}^b \\Phi\\left(\\left\\lfloor\\frac{n}{y}\\right\\rfloor\\right) - \\lfloor b \\rfloor\\Phi(\\lfloor a \\rfloor)$$\n",
|
||||
"\n",
|
||||
"By using this formula for $\\Phi(n)$, instead of calculating $n$ different totients, our sums only run to $a$ and $b$.\n",
|
||||
"This might seem complicated, but note that in the second summation on the right, when $y=1$, we have $\\Phi(n)$. We can extract that term and rearrange to get a recursive formula for $\\Phi(n)$:\n",
|
||||
"$$\\Phi(n) = \\frac{n(n+1)}{2} + \\lfloor b \\rfloor\\Phi(\\lfloor a \\rfloor) - \\sum_{x=1}^a \\phi(x)\\left\\lfloor\\frac{n}{x}\\right\\rfloor - \\sum_{y=2}^b \\Phi\\left(\\left\\lfloor\\frac{n}{y}\\right\\rfloor\\right)$$\n",
|
||||
"\n",
|
||||
"However, for this formula to be effective, we also need an efficient way to calculate $M(n)$. Fortunately, we can just apply the hyperbola method again, with the convolution $\\epsilon = \\mu * 1$, where $\\epsilon$ is the [unit identity](https://en.wikipedia.org/wiki/Unit_function). Once again, we choose $1 < a < n$ and let $b = n/a$.\n",
|
||||
"The major advantage of this formula is that we only need the values of the totient function up to $a$, instead of up to $n$. We can set $a = \\sqrt{1000000} = 1000$.\n",
|
||||
"\n",
|
||||
"$$\\sum_{k=1}^n \\epsilon(k) = \\sum_{k=1}^n \\sum_{xy=k} \\mu(x)$$\n",
|
||||
"\n",
|
||||
"$$1 = \\sum_{x=1}^a \\sum_{y=1}^{n/x} \\mu(x) + \\sum_{y=1}^b \\sum_{x=1}^{n/y} \\mu(x) - \\sum_{x=1}^a \\sum_{y=1}^b \\mu(x)$$\n",
|
||||
"\n",
|
||||
"$$1 = \\sum_{x=1}^a \\mu(x)\\left\\lfloor \\frac{n}{x}\\right\\rfloor + \\sum_{y=1}^b M\\left(\\left\\lfloor \\frac{n}{y}\\right\\rfloor\\right) - \\lfloor b \\rfloor M(\\lfloor a\\rfloor)$$\n",
|
||||
"\n",
|
||||
"$$M(n) = 1 + \\lfloor b\\rfloor M(\\lfloor a\\rfloor) - \\sum_{x=1}^a \\mu(x)\\left\\lfloor \\frac{n}{x}\\right\\rfloor - \\sum_{y=2}^b M\\left(\\left\\lfloor \\frac{n}{y}\\right\\rfloor\\right)$$\n",
|
||||
"\n",
|
||||
"Now we have a recursive implementation of $M(n)$. All that's left is to calculate the values of $\\mu(n)$ that we need. By taking advantage of $\\mu$ being [multiplicative](https://en.wikipedia.org/wiki/Multiplicative_function), we can compute values using a similar strategy to the sieve of Eratosthenes - see [problem 10](https://projecteuler.net/problem=10)."
|
||||
"This all leads to this code, yet another implementation of $\\Phi(n)$:"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 4,
|
||||
"id": "48c9275b",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"def mobius_range(limit):\n",
|
||||
" ms = [n for n in range(0, limit)]\n",
|
||||
"\n",
|
||||
" for n in range(0, limit):\n",
|
||||
" if n == 0 or n == 1 or ms[n] != n:\n",
|
||||
" yield ms[n]\n",
|
||||
" continue\n",
|
||||
" \n",
|
||||
" yield -1\n",
|
||||
" \n",
|
||||
" for k in range(2 * n, limit, n):\n",
|
||||
" if k % n ^ 2 == 0:\n",
|
||||
" ms[k] = 0\n",
|
||||
"\n",
|
||||
" ms[k] //= -n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "f12a8b30",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"We'll use $a = \\sqrt{1000000} = 1000$ as our upper bound."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 5,
|
||||
"id": "c2071ba1",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from math import isqrt\n",
|
||||
"mu = list(mobius_range(isqrt(1000000) + 1))"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "a8db5c70",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Now we can implement $M(n)$ with our recursive formula:"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 6,
|
||||
"id": "f04ed69a",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"@cache\n",
|
||||
"def M(n):\n",
|
||||
" if n == 0 or n == 1:\n",
|
||||
" return n\n",
|
||||
" \n",
|
||||
" a = sqrt(n)\n",
|
||||
" b = n / a\n",
|
||||
" \n",
|
||||
" total = 1 + floor(b) * M(floor(a))\n",
|
||||
" total -= sum(mu[x] * floor(n/x) for x in range(1, floor(a) + 1))\n",
|
||||
" total -= sum(M(floor(n/y)) for y in range(2, floor(b) + 1))\n",
|
||||
" \n",
|
||||
" return total"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "132dad8d",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"And finally, yet another implementation of $\\Phi(n)$:"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 7,
|
||||
"id": "394d604a",
|
||||
"metadata": {
|
||||
"scrolled": true
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"@cache\n",
|
||||
"def totient_sum(n):\n",
|
||||
" if n == 1:\n",
|
||||
" return 1\n",
|
||||
"\n",
|
||||
" a = sqrt(n)\n",
|
||||
" b = n / a\n",
|
||||
"\n",
|
||||
" total = sum(x * M(floor(n/x)) for x in range(1, floor(a) + 1))\n",
|
||||
" total += sum(polygonal_number(3, floor(n/y)) * mu[y] for y in range(1, floor(b) + 1))\n",
|
||||
" total -= M(floor(b)) * polygonal_number(3, floor(a))\n",
|
||||
"\n",
|
||||
" return total"
|
||||
" W = (n*(n+1)) // 2\n",
|
||||
" X = floor(b) * totient_sum(floor(a))\n",
|
||||
" Y = sum(euler_phi(x) * (n//x) for x in range(1, floor(a)+1))\n",
|
||||
" Z = sum(totient_sum(n//y) for y in range(2, floor(b)+1))\n",
|
||||
" return W + X - Y - Z"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -273,7 +187,7 @@
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 8,
|
||||
"execution_count": 5,
|
||||
"id": "55f0719b",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
@@ -283,7 +197,7 @@
|
||||
"303963552391"
|
||||
]
|
||||
},
|
||||
"execution_count": 8,
|
||||
"execution_count": 5,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
@@ -309,7 +223,7 @@
|
||||
],
|
||||
"metadata": {
|
||||
"kernelspec": {
|
||||
"display_name": "SageMath 9.5",
|
||||
"display_name": "SageMath 10.8",
|
||||
"language": "sage",
|
||||
"name": "sagemath"
|
||||
},
|
||||
@@ -323,7 +237,7 @@
|
||||
"name": "python",
|
||||
"nbconvert_exporter": "python",
|
||||
"pygments_lexer": "ipython3",
|
||||
"version": "3.11.2"
|
||||
"version": "3.12.5"
|
||||
}
|
||||
},
|
||||
"nbformat": 4,
|
||||
|
||||
Reference in New Issue
Block a user