【问题标题】:Compute series without being able to store values?在无法存储值的情况下计算系列?
【发布时间】:2015-07-27 21:44:39
【问题描述】:

问题陈述[here]

设 S 是整数的无限序列:

S0 = a; S1 = b;

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

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

( 0

我尝试了什么(这会产生运行时错误):

#include <bits/stdc++.h>
using namespace std;

long long int q,a,b,arr[100002];/*Can't declare an array of required size */
 
int main() {
    // your code goes here
    scanf("%lld%lld",&a,&b);
    arr[0]=a,arr[1]=b;
    scanf("%d",&q);
    int p[100002];
    long long int m = -1;//stores max index asked
    for(int i=0;i<q;i++)
    {
        scanf("%lld",&p[i]);
        m = (m>p[i])?m:p[i];
    }
    for(int i=2;i<=m;i++)//calculates series upto that index
    {
        arr[i]=abs(arr[i-1]-arr[i-2]);
    }
    for(int i=0;i<q;i++)
    {
        printf("%lld\n",arr[p[i]]);
    }
    return 0;
} 

Given : qi 适合 64 位整数。由于索引可能非常大并且我不能将该位声明为数组,我应该如何解决这个问题(因为蛮力会给出 TLE)。谢谢!

【问题讨论】:

  • 不要为parr 静态分配空间,除非你真的知道它们的大小,显然你事先并不知道。使用动态 c++ container(例如向量、列表、队列)仅存储您需要的值。只需在需要时推送新元素。
  • 好的,但是我如何生成系列,因为 qi 可能非常大,在这种情况下,这会带来麻烦。例如这种情况:10^18 10^1721000 10^15
  • 该系列只需要您知道最后两个条目 Si-1 和 Si-2。仅将这些值存储为标量并继续重用这些变量。
  • 除了 q 之外不要推送任何内容。这是阅读说明的练习。
  • 看起来@dpmcmlxxvi 刚刚想通了。

标签: c++ algorithm data-structures


【解决方案1】:

哈!有一个不需要(完整)迭代的解决方案:

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

那么保证它们的差值绝对值小于(或等于)两者中较大的一个。

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

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

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

b) 零超出范围。然后下一步将计算较大和计算的差异(等于较大)之间的差异,从而得到0。在下一步中,这将导致回到最初的 (*) 情况。

结果:LL0、...的重复模式

一些例子:

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];
}

Live demo here(如果您愿意,可以使用 ab 值)。

如果您对相同的 A 和 B 重复查询,您可以在 std::vector 中缓存直到 next == 0 的所有值,从而为以下查询提供真正恒定的时间。

我也很确定在序列到达 0 之前有一个模式,但我找不到它。


我刚刚注意到我错过了应该是差值的绝对值...

如果速度够快,这里有一个迭代版本:

// deciding on a concrete type is hard ...
uint64_t sequence (uint64_t A, uint64_t B, uint64_t n) {
 if (n == 0) {
  return A;
 }
 uint64_t prev[2] = {A, B};
 for (auto it = 1u; it < n; ++it) {
  auto next =
    (prev[0] > prev[1]) ?
      (prev[0] - prev[1]) :
      (prev[1] - prev[0]);
  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);
}

一些live demo is here,但在处理更大的数字时会出现问题(我怀疑 fib 不正确)。

演示还包含序列的迭代版本,作为控制。如果这对您来说足够快,请改用它。除了最后两个数字之外,无需存储任何内容。

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

【讨论】:

  • 您应该四舍五入到最接近的整数,而不是截断(假设您有足够的精度让该实现工作)。不过,对斐波那契数使用双加法肯定比浮点乘法更好。
  • @Hurkyl 你的意思是 fib 函数?在这种情况下它并没有改善它,我怀疑(结果)值溢出。
  • 当。当我看到答案时,我希望有人有办法绕过绝对值的破坏性并避免迭代。斐波那契捷径的绝佳解释。
  • @user4581301 不完全,但几乎不需要迭代。查看我的最新编辑 :)
  • @DanielJour 很好的答案。我只想补充一点,最好使用矩阵求幂而不是显式公式来计算 fib(n),这会导致一些精度误差
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2019-08-28
  • 2020-06-10
  • 2020-01-20
  • 2016-10-28
  • 2014-10-31
  • 2011-03-02
  • 2022-10-13
相关资源
最近更新 更多