Forgot password?
 Register account
View 246|Reply 1

[数列] 求$n$项数列的最短递推式

[Copy link]

3159

Threads

7941

Posts

610K

Credits

Credits
63770
QQ

Show all posts

hbghlyj Posted 2023-5-6 16:09 |Read mode
Wikipedia

定义

定义一个 $n$ 项数列 $\{a_0 \dots a_{n - 1} \}$ 的递推式为满足下式的序列 $\{r_0\dots r_m\}$:

$$\sum_{j = 0} ^ m r_j a_{i - j} = 0, \forall i \ge m$$

其中 $r_0 = 1$。$m$ 称为该递推式的阶数

数列 $\{a_i\}$ 的最短递推式即为阶数最小的递推式。

做法

与上面定义的稍有不同,这里定义一个新的递推系数 $\{f_0 \dots f_{m - 1}\}$,满足:

$$a_i = \sum_{j = 0} ^ {m - 1} f_j a_{i - j - 1}, \forall i \ge m$$

容易看出 $f_i = -r_{i + 1}$,并且阶数 $m$ 与之前的定义是相同的。

显然初始时有 $F_0 = \{\}$。假设递推系数 $F_{i - 1}$ 对数列 $\{a_i\}$ 的前 $i - 1$ 项均成立,这时对第 $i$ 项就有两种情况:

  1. 递推系数对 $a_i$ 也成立,这时不需要进行任何调整,直接令 $F_i = F_{i - 1}$ 即可。
  2. 递推系数对 $a_i$ 不成立,这时需要对 $F_{i - 1}$ 进行调整,得到新的 $F_i$。

设 $\Delta_i = a_i - \sum_{j = 0} ^ m f_{i - 1, j} a_{i - j - 1}$,即 $a_i$ 与 $F_{i - 1}$ 的递推结果的差值。

如果这是第一次对递推系数进行修改,则说明 $a_i$ 是序列中的第一个非零项。这时直接令 $F_i$ 为 $i$ 个 $0$ 即可,显然这是一个合法的最短递推式。

否则设上一次对递推系数进行修改时,已考虑的 $\{a_i\}$ 的项数为 $k$。如果存在一个序列 $G = \{g_0 \dots g_{m' - 1}\}$,满足:

$$\sum_{j = 0} ^ {m' - 1} g_j a_{i' - j - 1} = 0, \forall i' \in [m', i)$$

并且 $\sum_{j = 0} ^ {m' - 1} g_j a_{i - j - 1} = \Delta_i$,那么不难发现将 $F_k$ 与 $G$ 按位分别相加之后即可得到一个合法的递推系数 $F_i$。

考虑如何构造 $G$。一种可行的构造方案是令

$$G = \{0, 0, \dots, 0, \frac{\Delta_i}{\Delta_k}, -\frac{\Delta_i}{\Delta_k}F_k\}$$

其中前面一共有 $i - k - 1$ 个 $0$,且最后的 $-\frac{\Delta_i}{\Delta_k} F_k$ 表示将 $F_k$ 每项乘以 $-\frac{\Delta_i}{\Delta_k}$ 后接在序列后面。

不难验证此时 $\sum_{j = 0} ^ {m' - 1} g_j a_{i - j - 1} = \Delta_k \frac{\Delta_i}{\Delta_k} = \Delta_i$,因此这样构造出的是一个合法的 $G$。将 $F_i$ 赋值为 $F_k$ 与 $G$ 逐项相加后的结果即可。

如果要求的是符合最开始定义的递推式 $\{r_i\}$,则将 $\{f_j\}$ 全部取相反数后在最开始插入 $r_0 = 1$ 即可。

从上述算法流程中可以看出,如果数列的最短递推式的阶数为 $m$,则算法的复杂度为 $O(nm)$。最坏情况下 $m = O(n)$,因此算法的最坏复杂度为 $O(n^2)$。

在实现算法时,由于每次调整递推系数时都只需要用到上次调整时的递推系数 $F_k$,因此如果只需要求整个数列的最短递推式,可以只存储当前递推系数和上次调整时的递推系数,空间复杂度为 $O(n)$。

参考实现

  1. vector<int> solve_sparse_equations(const vector<tuple<int, int, int> > &A,
  2. const vector<int> &b) {
  3. int n = (int)b.size(); // 0-based
  4. vector<vector<int> > f({b});
  5. for (int i = 1; i < 2 * n; i++) {
  6. vector<int> v(n);
  7. auto &u = f.back();
  8. for (auto [x, y, z] : A) // [x, y, value]
  9. v[x] = (v[x] + (long long)u[y] * z) % p;
  10. f.push_back(v);
  11. }
  12. vector<int> w(n);
  13. mt19937 gen;
  14. for (auto &x : w) x = uniform_int_distribution<int>(1, p - 1)(gen);
  15. vector<int> a(2 * n);
  16. for (int i = 0; i < 2 * n; i++)
  17. for (int j = 0; j < n; j++) a[i] = (a[i] + (long long)f[i][j] * w[j]) % p;
  18. auto c = berlekamp_massey(a);
  19. int m = (int)c.size();
  20. vector<int> ans(n);
  21. for (int i = 0; i < m - 1; i++)
  22. for (int j = 0; j < n; j++)
  23. ans[j] = (ans[j] + (long long)c[m - 2 - i] * f[i][j]) % p;
  24. int inv = power(p - c[m - 1], p - 2);
  25. for (int i = 0; i < n; i++) ans[i] = (long long)ans[i] * inv % p;
  26. return ans;
  27. }
Copy the Code

朴素的 Berlekamp-Massey 算法求解的是有限项数列的最短递推式。如果待求递推式的序列有无限项,但已知最短递推式的阶数上界,则只需取出序列的前 $2m$ 项即可求出整个序列的最短递推式。(证明略)

OI-wiki

3159

Threads

7941

Posts

610K

Credits

Credits
63770
QQ

Show all posts

 Author| hbghlyj Posted 2023-5-6 16:28

例子

mzhang2021

数列 $s = \{1, 2, 4, 8, 13, 20, 28, 215, 757, 2186\}$. 初始递推为空 $c = \{\}$.

让我们从处理 $i = 0$ 开始。我们有 $c(0) = 0 \neq 1 = s_0$(回想一下,如果递推 $c$ 为空,则 $c(i) = 0$)。嗯,那是不对的。由于这是我们第一次初始化 $c$,设 $c = \{1\}$。

接下来处理 $i = 1$。我们有 $c(1) = c_1 \cdot s_{1-1} = 1 \cdot 1 = 1 \neq 2 = s_1$。不幸的是递归关系是错误的,我们必须调整。对此有一个简单的修复:改为 $c = \{2\}$。现在,$c(1) = 2 \cdot 1 = 2$ 就像我们想要的那样。

接下来,我们处理 $i = 2$。我们有 $c(2) = c_1 \cdot s_{2-1} = 2 \cdot 2 = 4 = s_2$。看起来我们的 $c$ 有效,所以没有必要修改它。

接下来,$i = 3$。$c(3) = c_1 \cdot s_{3-1} = 2 \cdot 4 = 8 = s_3$。这行得通,所以我们不做任何改变。

接下来,$i = 4$。$c(4) = c_1 \cdot s_{4-1} = 2 \cdot 8 = 16 \neq 13 = s_3$。我们需要调整。如果我们令 $c = \{13 / 8\}$ 会怎样?问题是,现在 $c(4) = 13$ 就像我们想要的那样,但是 $c(3)$ 是错误的!我们不能再假设线性递归的长度仅为 1。我们需要更复杂的东西。

让我们更具体地说明我们想要什么。我们想要某个序列 $c' = c + d$ 使得 $c'(i)$ 对所有 $i \leq 4$ 的计算正确。 所以我们想要某个 $d$ 使得 $d(i) = 0$ for $i < 4$ 和 $d(4) = s_4 - c(4) = -3$。我们将 $d(4)$ 的预期值表示为 $\Delta$。

Here’s the trick to generate such a $d$: let’s keep track of each previous version of $c$ and which index it failed on. So for example, we have $\{\}$ which failed at index 0 and $\{1\}$ which failed at index 1. Let’s consider the second version of $c$, the $\{1\}$ sequence that failed at index 1. Let’s denote the failure index as $f$. Here’s what we’ll do:

  1. Set $d$ equal to that sequence.
  2. Multiply the sequence by $-1$.
  3. Insert a 1 on the left.
  4. Multiply the sequence by $\frac{\Delta}{d(f + 1)} = \frac{-3}{1} = -3$.
  5. Insert $i - f - 1 = 4 - 1 - 1 = 2$ zeros on the left.
$d$ after each step

After Step 1: $d = \{1\}$

After Step 2: $d = \{-1\}$

After Step 3: $d = \{1, -1\}$

After Step 4: $d = \{-3, 3\}$

After Step 5: $d = \{0, 0, -3, 3\}$


所以我们有 $d = \{0, 0, -3, 3\}$。这看起来很随意,但让我们计算 $d$ 看看会发生什么:

\[d(4) = d_1 s_{4-1} + d_2 s_{4-2} + d_3 s_{4-3} + d_4 s_{4-4} = 0 \cdot 8 + 0 \cdot 4 - 3 \cdot 2 + 3 \cdot 1 = -3\]

对于 $i < 4$,$d$ 是未定义的,因为它是长度为 4 的序列。所以我猜这 $d$ 有效。因此,我们的新递推是 $c + d = \{2\} + \{0, 0, -3, 3\} = \{2, 0, -3, 3\}$.

Let’s keep going. Our new $c$ will work for $i = 5$ and $i = 6$, but fail for $i = 7$.

\[c(7) = c_1 s_{7-1} + c_2 s_{7-2} + c_3 s_{7-3} + c_4 s_{7-4} = 2 \cdot 28 + 0 \cdot 20 - 3 \cdot 13 + 3 \cdot 8 = 41 \neq 215 = s_7\]

We need some $d$ to add to $c$ such that $d(i) = 0$ for $i < 7$ and $d(7) = s_7 - c(7) = 174$.

Once again, let’s look at the past versions of $c$:

  • $\{\}$ which failed at index 0.
  • $\{1\}$ which failed at index 1.
  • $\{2\}$ which failed at index 4.

This time, we’ll consider the third version of $c$, the $\{2\}$ sequence that failed at index 4 (I’ll explain which version you pick in a moment). We will apply the following seemingly arbitrary steps:

  1. Set $d$ equal to our chosen sequence.
  2. Multiply the sequence by $-1$.
  3. Insert a 1 on the left.
  4. Multiply the sequence by $\frac{\Delta}{d(f + 1)} = \frac{174}{-3} = -58$.
  5. Insert $i - f - 1 = 7 - 4 - 1 = 2$ zeros on the left.
$d$ after each step

After Step 1: $d = \{2\}$

After Step 2: $d = \{-2\}$

After Step 3: $d = \{1, -2\}$

After Step 4: $d = \{-58, 116\}$

After Step 5: $d = \{0, 0, -58, 116\}$


我们的 $d$ 有效吗?让我们验证。

\begin{align*} d(7) &= d_1 s_{7-1} + d_2 s_{7-2} + d_3 s_{7-3} + d_4 s_{7-4} = 0 \cdot 28 + 0 \cdot 20 - 58 \cdot 13 + 116 \cdot 8 = 174 \\ d(6) &= d_1 s_{6-1} + d_2 s_{6-2} + d_3 s_{6-3} + d_4 s_{6-4} = 0 \cdot 20 + 0 \cdot 13 - 58 \cdot 8 + 116 \cdot 4 = 0 \\ d(5) &= d_1 s_{5-1} + d_2 s_{5-2} + d_3 s_{5-3} + d_4 s_{5-4} = 0 \cdot 13 + 0 \cdot 8 - 58 \cdot 4 + 116 \cdot 2 = 0 \\ d(4) &= d_1 s_{4-1} + d_2 s_{4-2} + d_3 s_{4-3} + d_4 s_{4-4} = 0 \cdot 8 + 0 \cdot 4 - 58 \cdot 2 + 116 \cdot 1 = 0 \end{align*}

天哪,这真的有效! 所以我们将 $d$ 添加到旧的 $c$ 以获得新的 $c$:$\{2, 0, -3, 3\} + \{0, 0, -58, 116\} = \{2 , 0, -61, 119\}$。

最后,我们处理 $i = 8$ 和 $i = 9$,发现递归对它们都是正确的。所以 $c = \{2, 0, -61, 119\}$ 是我们的最终答案。

Mobile version|Discuz Math Forum

2025-5-31 11:02 GMT+8

Powered by Discuz!

× Quick Reply To Top Edit