我正在实施p + 1分解算法。为此,我需要计算由以下定义的卢卡序列的元素:
(1) x_0 = 1, x_1 = a
(2) x_n+l = 2 * a * x_n - x_n-l
我以递归方式实现了它(C#),但对于更大的索引来说效率很低。
static BigInteger Lucas(BigInteger a, BigInteger Q, BigInteger N)
{
if (Q == 0)
return 1;
if (Q == 1)
return a;
else
return (2 * a * Lucas(a, Q - 1, N) - Lucas(a, Q - 2, N)) % N;
}
我也知道
(3) x_2n = 2 * (x_n)^2 - 1
(4) x_2n+1 = 2 * x_n+1 * x_n - a
(5) x_k(n+1) = 2 * x_k * x_kn - x_k(n-1)
(3)和(4)应该有助于计算更大的Qs。但我不确定如何。 以某种方式使用Q的二进制形式我认为。
感谢任何帮助。
答案 0 :(得分:3)
Here我们可以看到如何使用矩阵驱动矩阵找到Nth Fibbonaci数
n
(1 1)
(1 0)
你可以利用这种方法来计算卢卡斯数,使用矩阵(对于你的情况x_n+l = 2 * a * x_n - x_n-l
)
n
(2a -1)
(1 0)
注意矩阵的N次方可以通过exponentiation by squaring
找到log(N)矩阵乘法答案 1 :(得分:1)
(3) x_2n = 2 * (x_n)^2 - 1
(4) x_2n+1 = 2 * x_n+1 * x_n - a
每当您看到2n
时,您应该认为“这可能表示偶数”,同样2n+1
可能意味着“这是一个奇数”。
您可以修改x
索引,以便左侧有n
(以便更容易理解这对应于递归函数调用),只需要小心舍入。
3) 2n n
=> n n/2
4) it is easy to see that if x = 2n+1, then n = floor(x/2)
and similarly n+1 = ceil(x/2)
因此,对于#3,我们有:(伪代码)
if Q is even
return 2 * (the function call with Q/2) - 1
#4:
else // following from above if
return 2 * (the function call with floor(Q/2))
* (the function call with ceil(Q/2)) - a
然后我们还可以合并一些memoization以防止多次计算相同参数的返回值:
Q
值的地图以返回值。Q
的值。如果是,则返回相应的返回值。Q
的值和返回值添加到地图中。答案 2 :(得分:0)
第n个卢卡斯数字的值为:
Exponentiation by squaring可用于评估函数。例如,如果n = 1000000000,则n = 1000 * 1000 ^ 2 = 10 * 10 ^ 2 * 1000 ^ 2 = 10 * 10 ^ 2 *(10 * 10 ^ 2)^ 2。通过这种方式简化,您可以大大减少计算次数。
答案 3 :(得分:0)
你可以得到一些改进(只有一百万......),而不需要真正花哨的数学。
首先,让数据流更加明确:
static BigInteger Lucas(BigInteger a, BigInteger Q, BigInteger N)
{
if (Q == 0)
{
return 1;
}
else if (Q == 1)
{
return a;
}
else
{
BigInteger q_1 = Lucas(a, Q - 1, N);
BigInteger q_2 = Lucas(a, Q - 2, N);
return (2 * a * q_1 - q_2) % N;
}
}
不出所料,这并没有真正改变性能。
然而,它确实表明我们只需要两个先前的值来计算下一个值。这让我们可以将函数颠倒过来变成迭代版本:
static BigInteger IterativeLucas(BigInteger a, BigInteger Q, BigInteger N)
{
BigInteger[] acc = new BigInteger[2];
Action<BigInteger> push = (el) => {
acc[1] = acc[0];
acc[0] = el;
};
for (BigInteger i = 0; i <= Q; i++)
{
if (i == 0)
{
push(1);
}
else if (i == 1)
{
push(a);
}
else
{
BigInteger q_1 = acc[0];
BigInteger q_2 = acc[1];
push((2 * a * q_1 - q_2) % N);
}
}
return acc[0];
}
可能有更清晰的方式来写这个,但它有效。它也快得多。它的速度要快得多,这是一种不切实际的衡量标准。在我的系统上,Lucas(4000000, 47, 4000000)
大约需要30分钟,而IterativeLucas(4000000, 47, 4000000)
大约需要2毫秒。我想比较48,但我没有耐心。
你可以使用模运算的这些属性来挤出更多(可能是两倍?)
(a + b) % n = (a%n + b%n) % n
(a * b) % n = ((a%n) * (b%n)) % n
如果你应用这些,你会发现a%N
发生了几次,所以你可以通过在循环之前预先计算一次来获胜。当a
远大于N
时,这尤其有用;我不确定你的申请是否会发生这种情况。
可能有一些聪明的数学技术可以将这个解决方案从水中吹出来,但我认为这样的改进只需通过改变一些代码即可实现。