C++ 计算序列而不能存储值?

C++ 计算序列而不能存储值?,c++,algorithm,data-structures,C++,Algorithm,Data Structures,问题陈述[] 设S为整数的无穷余: S0=a; S1=b Si=| Si-2-Si-1 |对于所有i>=2 你有两个整数a和b。您必须回答有关序列中第n个元素的一些查询。(表示打印序列中的第n个数字,即S(n)) (0HA!有一种解决方案不需要(完全)迭代: 考虑一些值Si和Sj,其中i,j>1。然后,看看序列的数字是如何建立的(使用绝对值),我们可以得出结论,这两个数字都是正的 然后保证其差值的绝对值小于(或等于)两者中的较大值 假设严格小于两个中的较大值,在接下来的两个步骤中,原始值的较大值

问题陈述[]

设S为整数的无穷余:

S0=a; S1=b

Si=| Si-2-Si-1 |对于所有i>=2

你有两个整数a和b。您必须回答有关序列中第n个元素的一些查询。(表示打印序列中的第n个数字,即S(n))


(0HA!有一种解决方案不需要(完全)迭代:

考虑一些值
Si
Sj
,其中
i,j>1
。然后,看看序列的数字是如何建立的(使用绝对值),我们可以得出结论,这两个数字都是正的

然后保证其差值的绝对值小于(或等于)两者中的较大值

假设严格小于两个中的较大值,在接下来的两个步骤中,原始值的较大值将“超出范围”。由此我们可以得出结论,在这种情况下,序列的数量越来越小

(*)如果差值等于较大的数值,则另一个数值必须是
0
。在下一步中,可能会发生以下两种情况之一:

a) 越大超出范围,那么接下来的两个数字是计算出的差值(等于越大)和0,这将再次产生较大的值。那么我们的情况和…一样

b) 零超出范围。然后,下一步将计算较大值与计算出的差值(等于较大值)之间的差值,结果为
0
。在下一步中,这将返回到原始的(*)情况

结果:重复模式为
L
L
0

一些例子:

3, 1, 2, 1, 1, 0, 1, 1, 0, ...
1, 3, 2, 1, 1, 0, 1, 1, 0, ...
3.5, 1, 2.5, 1.5, 1, .5, .5, 0, .5, .5, 0, ...
.1, 1, .9, .1, .8, .7, .1, .6, .5, .1, .4, .3, .1, .2, .1, .1, 0, ...
将其应用于代码:只要一个值为
0
,就不需要再进行迭代,接下来的两个数字将与前一个相同,然后将再次出现
0
,依此类推:

// A and B could also be negative, that wouldn't change the algorithm,
// but this way the implementation is easier
uint64_t sequence(uint64_t A, uint64_t B, size_t n) {
 if (n == 0) {
  return A;
 }
 uint64_t prev[2] = {A, B};
 for (size_t it = 1u; it < n; ++it) {
  uint64_t next =
    (prev[0] > prev[1]) ?
      (prev[0] - prev[1]) :
      (prev[1] - prev[0]);
  if (next == 0) {
   size_t remaining = n - it - 1;
   if (remaining % 3 == 0) {
    return 0;
   }
   return prev[0]; // same as prev[1]
  }
  prev[0] = prev[1];
  prev[1] = next;
 }
 return prev[1];
}
正如您看到的,您不需要存储所有值,只需要最后两个数字就可以计算下一个

如果速度不够快,您可以添加记忆:将
prev
值对存储在有序的
std::map
(映射
n
)中。然后,您可以从条目开始,使用下一个较低的值
n
,而不是从开头开始。当然,你也需要管理这张地图:让它保持小并且充满“有用”的值


这不是一个编程问题,而是一个算法问题。让我们看一下该序列的第一个数字:

a
b
a-b
b-(a-b) = 2b-a
(a-b)-(b-(a-b)) = 2(a-b)-b = 2a-3b
2b-a-(2a-3b) = 5b-3a
2a-3b-(5b-3a) = 5a-8b
...
只看系数的绝对值就可以看出

b: 0 1 1 2 3 5 8 ...
a: (1) 0 1 1 2 3 5 ...
。。。这是关于斐波那契序列的。还有一个标志,但这很简单:

b: - + - + - ...
a: + - + - + ...
所以序列中的第n个数字应该等于

f(0) = a
f(n) = (-1)^n      * fib(n-1) * a +
       (-1)^(n-1)  * fib(n)   * b
当然,现在我们必须计算第n个斐波那契数,但幸运的是,已经有了一个解决方案:

fib(n) = (phi^n - chi^n) / (phi - chi)
   with
  phi = (1 + sqr(5)) / 2
  chi = 1 - phi
所以,把它带到代码中:

unsigned long fib(unsigned n) {
 double const phi = (1 + sqrt(5)) / 2.0;
 double const chi = 1 - phi;
 return (pow(phi, n) - pow(chi, n)) / (phi - chi);
}
long sequence (long A, long B, unsigned n) {
 if(n ==0) {
  return A;
 }
 auto part_a = fib(n-1) * A;
 auto part_b = fib (n) * B;
 return (n % 2 == 0) ? (part_a - part_b) : (part_b - part_a);
}
有些,但当接近更大的数字时,这会有问题(我怀疑这个谎言是不正确的)

演示还包含序列的迭代版本,作为控件。如果这对你来说足够快,就用它代替。不需要存储超过最后两个数字的任何内容


为了进一步改进这一点,您可以使用一个带有孔的查找表来查找斐波那契数,即记住序列的每十分之一(及其后续)个数。

不要静态地为
p
arr
分配空间,除非您确实知道它们的大小,而这显然是您事先不知道的。使用动态C++(例如,向量、列表、队列)只存储所需的值。只要根据需要推出新元素。好吧,但我如何生成序列,因为qi可能非常大,在这种情况下,这将给出一个tle。例如本例:
10^18 10^17
2
1000 10^15
本系列只要求您知道最后两个条目Si-1和Si-2。只将这些值存储为标量,并继续重用这些变量。这是一个阅读说明的练习。看起来@dpmcmlxxvi刚刚解决了这个问题。您应该舍入到最接近的整数,而不是截断(假设您有足够的精度使该实现能够工作)。当然,对Fibonacci数使用double和add方法比使用浮点乘法更好。@Hurkyl你是说对fib函数使用double和add方法?在本例中,它并没有改进它,我怀疑(result)值为overflow.Dang。当我看到答案时,我希望有人能想出一个诀窍,绕过绝对值的破坏性,避免迭代。很好地解释了短切斐波那契。@user4581301不完全,但几乎不需要迭代。见我的最新编辑:)@DanielJour回答得很好。我只想补充一点,使用矩阵求幂计算fib(n)比使用显式公式计算fib(n)更好,因为显式公式可能会导致一些精度错误
fib(n) = (phi^n - chi^n) / (phi - chi)
   with
  phi = (1 + sqr(5)) / 2
  chi = 1 - phi
unsigned long fib(unsigned n) {
 double const phi = (1 + sqrt(5)) / 2.0;
 double const chi = 1 - phi;
 return (pow(phi, n) - pow(chi, n)) / (phi - chi);
}
long sequence (long A, long B, unsigned n) {
 if(n ==0) {
  return A;
 }
 auto part_a = fib(n-1) * A;
 auto part_b = fib (n) * B;
 return (n % 2 == 0) ? (part_a - part_b) : (part_b - part_a);
}