问题陈述[here]
让S成为整数的无限保证:
S0 = a; S1 = b;
Si = | Si-2-Si-1 |对于所有i> = 2。
你有两个整数a和b。您必须回答有关序列中第n个元素的一些查询。(表示打印序列中的第n个数字,即S(n))
(0 <= a,b <= 10 ^ 18),(1 <= q <= 100000)
我尝试了什么(这会产生运行时错误):
#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;
}
鉴于:qi适合64位整数。因为索引可能非常大并且我不能声明该位是一个数组,我应该如何处理这个问题(因为蛮力会给TLE)。谢谢!
答案 0 :(得分:4)
HA!有一个解决方案不需要(完成)迭代:
考虑一些值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];
}
Live demo here(如果您愿意,可以使用a
和b
值。
如果您对同一个A和B重复查询,则可以将next == 0
中的所有值缓存到std::vector
,从而为以下查询提供真正恒定的时间。
我也很确定在序列到达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 ...
......这是关于Fibonacci序列的。然后,还有标志,但这很容易:
b: - + - + - ...
a: + - + - + ...
因此序列中的第n个数字应等于
f(0) = a
f(n) = (-1)^n * fib(n-1) * a +
(-1)^(n-1) * fib(n) * b
当然,现在我们必须计算第n个Fibonacci数,但幸运的是已经有了解决方案:
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,但是当接近更大的数字时会出现问题(我怀疑这个结果不正确)。
该演示还包含序列的迭代版本,作为控件。如果这对你来说足够快,那就改用它。无需存储超过最后两个数字的内容。
为了进一步改善这一点,你可以使用一个带有斐波那契数字孔的查找表,即记住序列的每十分之一(及其后续)数。