Блог пользователя duckladydinh

Автор duckladydinh, история, 15 месяцев назад, По-английски

Hi


Update 4

Andycraft has provided the solution here computing Catalan using FFT + CDQ which is actually not very scary.

Bonus: For a slightly different self full convolution where f[n + 1] = f[i]f[j] for i + j <= n, it could be achieved by adding a simple change to his code

    if l == r:
      if l > 1:
        f[l] += f[l - 1]
      return

I can't wait looking how to expand this.

Update 3

Actually no, the below code is at least O(n^2logn), not O(N(logN)^2), back to 0, then :(.

Update 2

Finally, after some desperate struggling, I am able to do it (I feel so old for challenges already :()

import numpy as np


def catalan(n: int):
  f = np.zeros(n + 1).astype(int)
  g = np.zeros(n + 1).astype(int)
  f[0] = 1
  for i in range(n):
    two_pow = 1
    while True:
      ay = i + 1 - two_pow
      ax = max(0, ay - two_pow + 1)      

      bx = two_pow - 1
      by = min(i, bx + two_pow - 1)

      c = np.convolve(f[ax : ay + 1], f[bx : by + 1])
      g[i] += c[i - (ax + bx)]

      if ax == 0: break
      two_pow *= 2

    f[i + 1] = g[i]

  return f[:n]


print(catalan(12))

Update 1

Thanks to conqueror_of_tourist, the search term is Online FFT.

Unfortunately... it's quite beyond me to understand it. I have been reading and this is the best code I could draft but it's not working (returning 12 instead of 14). Anyone familiar, can you spot anything wrong? Thanks.

import numpy as np


def catalan(n: int):
  f = np.zeros(n + 1).astype(int)
  f[0] = 1
  for i in range(n - 1):
    f[i + 1] += f[i]
    f[i + 2] += f[i]
    two_pow = 2
    while i > 0 and i % two_pow == 0:
      a = f[i - two_pow : i]
      b = f[two_pow : min(n, two_pow * 2)]
      two_pow *= 2

      contrib = np.convolve(a, b)
      for j in range(min(len(contrib), n - i)):
        f[i + j + 1] += contrib[j]

  return f[:n]


print(catalan(10))

I am learning FFT. ChatGPT told me that FFT could assist in solving self-convolution form like Catalan number where the (n+1)-th element is some convolution of the first n elements. For example:

c[n + 1] = sum(c[i][n — i]) for i in 0..n

Unfortunately, no matter where I look (and how hard I press ChatGPT), I couldn't find a single website/book/paper detailing this approach.

My Question: Is it actually possible to compute the first n elements of a self-convolution form like Catalan using FFT in, for example, O(nlogn) or less than O(n^2)?

Thanks a lot

  • Проголосовать: нравится
  • +19
  • Проголосовать: не нравится

»
15 месяцев назад, скрыть # |
 
Проголосовать: нравится 0 Проголосовать: не нравится

Auto comment: topic has been updated by duckladydinh (previous revision, new revision, compare).

»
15 месяцев назад, скрыть # |
 
Проголосовать: нравится +23 Проголосовать: не нравится

On Codeforces (and in competitive programming) this is usually known as 'Online FFT'. You should be able to find CF blog posts on this topic solving it in $$$O(n\log^2{n})$$$.

»
15 месяцев назад, скрыть # |
 
Проголосовать: нравится 0 Проголосовать: не нравится

Suppose we have

$$$a_0=c,\quad a_{n+1}=\sum_{i=0}^{n}a_i b_{n-i}$$$

Then the generating function A(x) satisfies A=ABx+c and therefore we may invert 1-Bx to find a in O(nlogn).

  • »
    »
    15 месяцев назад, скрыть # ^ |
     
    Проголосовать: нравится 0 Проголосовать: не нравится

    Thanks, actually I am more interested in the Online FFT part for now as it is already quite beyond me :) so it's not time to proceed with generating function.../inversion... yet.

    I have been reading and was trying to draft a piece of code to compute catalan... but it didn't work. I put it in the post, if u are familiar with even more advanced variants, do u maybe notice if I code something obviously wrong?

    Thanks a lot

»
15 месяцев назад, скрыть # |
 
Проголосовать: нравится 0 Проголосовать: не нравится

Auto comment: topic has been updated by duckladydinh (previous revision, new revision, compare).

»
15 месяцев назад, скрыть # |
 
Проголосовать: нравится 0 Проголосовать: не нравится

Auto comment: topic has been updated by duckladydinh (previous revision, new revision, compare).

»
15 месяцев назад, скрыть # |
 
Проголосовать: нравится 0 Проголосовать: не нравится

Auto comment: topic has been updated by duckladydinh (previous revision, new revision, compare).

»
15 месяцев назад, скрыть # |
 
Проголосовать: нравится 0 Проголосовать: не нравится

Auto comment: topic has been updated by duckladydinh (previous revision, new revision, compare).

»
15 месяцев назад, скрыть # |
 
Проголосовать: нравится 0 Проголосовать: не нравится

The following Python code works for calculating Catalan using online FFT.

from numpy import *

def catalan(n: int):
    f = [0] * (n + 1)
    f[0] = 1

    # calculate the contribution of f[l1:r1+1]*f[l2:r2+1] => f[l:r+1]
    def contrib(l1, r1, l2, r2, l, r):
        if l1 + l2 + 1 > r or r1 + r2 + 1 < l or l1 > r1 or l2 > r2 or l > r:
            return
        u = f[l1 : r1 + 1]
        v = f[l2 : r2 + 1]
        res = convolve(u, v)
        for i in range(l, r + 1):
            if i - 1 - l1 - l2 in range(0, len(res)):
                f[i] += res[i - 1 - l1 - l2]

    # CDQ on [l, r]
    def work(l, r):
        if l == r:
            return
        mid: int = (l + r) // 2
        work(l, mid)
        t = min([r - l, l - 1])
        contrib(0, t, l, mid, mid + 1, r)
        contrib(l, mid, 0, t, mid + 1, r)  # same as contrib(0, t, l, mid, mid + 1, r)
        contrib(l, mid, l, mid, mid + 1, r)

        work(mid + 1, r)

    work(0, n)
    return list(map(int, f))

print(catalan(20))
  • »
    »
    14 месяцев назад, скрыть # ^ |
     
    Проголосовать: нравится 0 Проголосовать: не нравится

    Thank you very much, Andycraft. Thanks to your code, I am able to understand the CDQ too (well, quite a lot of time reading and thinking as I am too old :)). It seems like the CDQ model just differs from the normal divide and conquer by moving contributions from left (and before) to right and limiting the contribution to a given [l, r] at all times and makes it easier to use together with this convolution thing (adamant@ post looks a bit scary to me so I thought it was some complex math thing and stayed away :)).

    Thanks a lot, short code is definitely the most expressive language for programmers :).

»
14 месяцев назад, скрыть # |
 
Проголосовать: нравится 0 Проголосовать: не нравится

Auto comment: topic has been updated by duckladydinh (previous revision, new revision, compare).

»
14 месяцев назад, скрыть # |
 
Проголосовать: нравится 0 Проголосовать: не нравится

Auto comment: topic has been updated by duckladydinh (previous revision, new revision, compare).

»
14 месяцев назад, скрыть # |
 
Проголосовать: нравится 0 Проголосовать: не нравится

Auto comment: topic has been updated by duckladydinh (previous revision, new revision, compare).

»
13 месяцев назад, скрыть # |
 
Проголосовать: нравится 0 Проголосовать: не нравится

If you're still interested in Online FFT, the following blog post might be helpful. (It's in Japanese, but you can get an intuitive understanding of how it works just by looking at the code and images.)

https://qiita.com/Kiri8128/items/1738d5403764a0e26b4c

»
13 месяцев назад, скрыть # |
 
Проголосовать: нравится +1 Проголосовать: не нравится

Since it seems that nobody has answered the question about FFT being used to solve catalan-like self convolutions in O(NlogN): Yes, it can!

Basically you have to find the generating function then use FFT to compute it fast.

Let's say H = sum Catalan[i] * x^i. We know that Catalan[N] = sum i < n Catalan[i] * Catalan[N-i-1]. H^2 * x = sum over 1 <= i of {sum over j < i of Catalan[j] * Catalan[i-j-1] * x^i} which is close to H. H^2 * x = sum over 1 <= i of {Catalan[i] * x^i}. Thus H^2 * x + 1 = H.

From here it's a matter of simplifying it: H^2 * x — H + 1 = 0 -> H = (1 +- sqrt(1 — 4x)) / 2x. We chose the solution that satisfies our conditions. Since lim x->0 (1 — sqrt(1 — 4x)) / 2x is 0 and lim x->0 (1 + sqrt(1 — 4x)) / 2x = 1, we take the second solution. Reminder that when we evaluate something at 0 we take the value of the coeficient of x^0 of its power series which in this case should be Catalan[0] which is 1.

After this, you can compute sqrt(F(x)) in O(NlogN) using FFT as explained in the editorial of the problem I linked below. You can read more details on https://cp-algorithms.com/algebra/polynomial.html#newtons-method although it doesn't explain exactly how to do it for sqrt.

This result is well known, it's even in wikipedia https://en.wikipedia.org/wiki/Catalan_number#First_proof and if you want a similar but harder/more dynamic exercise then solve https://mirror.codeforces.com/problemset/problem/438/E

If you're annoyed at the lack of latex, message me in discord with proper latex for this and I'll edit it ;)

  • »
    »
    13 месяцев назад, скрыть # ^ |
    Rev. 3  
    Проголосовать: нравится +8 Проголосовать: не нравится

    no one asked me

    Spoiler

    Since it seems that nobody has answered the question about FFT being used to solve catalan-like self convolutions in $$$O(n \log n)$$$: Yes, it can!

    Basically you have to find the generating function then use FFT to compute it fast.

    Let's say $$$H = \sum\limits_i \text{C}_i \cdot x^i$$$. We know that $$$\text{C}_n= \sum\limits_{ i \lt n} \text{C}_i \cdot \text{C}_{n-i-1}$$$.

    $$$H^2 \cdot x = \sum\limits_{1 \le i} \sum\limits_ {j \lt i} \text{C}_j \cdot \text{C}_{i-j-1} \cdot x^i$$$ which is close to $$$H$$$.

    $$$H^2 \cdot x = \sum\limits_{1 \le i} \text{C}_i \cdot x^i$$$. Thus $$$H^2 \cdot x + 1 = H$$$.

    From here it's a matter of simplifying it: $$$H^2 \cdot x - H + 1 = 0$$$ ==> $$$ H = \frac{1 ± \sqrt{1 - 4x}}{2x}$$$. We chose the solution that satisfies our conditions. Since $$$\lim\limits_{x→0} \frac{1 -\sqrt{1 - 4x}}{2x}$$$ is $$$0$$$ and $$$\lim\limits_{x→0} \frac{1 + \sqrt{1 — 4}}{2x} = 1$$$, we take the second solution. Reminder that when we evaluate something at $$$0$$$ we take the value of the coeficient of $$$x^0$$$ of its power series which in this case should be $$$C_0$$$ which is $$$1$$$.

    After this, you can compute $$$\sqrt{F(x)}$$$ in $$$O(n\log n)$$$ using FFT as explained in the editorial of the problem I linked below. You can read more details on https://cp-algorithms.com/algebra/polynomial.html#newtons-method although it doesn't explain exactly how to do it for sqrt.

    This result is well known, it's even in wikipedia https://en.wikipedia.org/wiki/Catalan_number#First_proof and if you want a similar but harder/more dynamic exercise then solve https://mirror.codeforces.com/problemset/problem/438/E