ACM OJ题解目录
本题网址:https://cn.vjudge.net/problem/HDU-1005
Number Sequence
A number sequence is defined as follows:
f(1) = 1, f(2) = 1, f(n) = (A * f(n - 1) + B * f(n - 2)) mod 7.
Given A, B, and n, you are to calculate the value of f(n).
Input
The input consists of multiple test cases. Each test case contains 3 integers A, B and n on a single line (1 <= A, B <= 1000, 1 <= n <= 100,000,000). Three zeros signal the end of input and this test case is not to be processed.
Output
For each test case, print the value of f(n) on a single line.
Sample Input
1 1 3
1 2 10
0 0 0
Sample Output
2
5
题目大意:
这题就是一道斐波那契的变种。第一项和第二项依然是1,递推公式变为:
f
i
b
[
i
]
=
(
A
∗
f
i
b
[
i
−
1
]
+
B
∗
f
i
b
[
i
−
2
]
)
%
7
fib[i] = (A*fib[i-1]+B*fib[i-2]) \% 7
fib[i]=(A∗fib[i−1]+B∗fib[i−2])%7。
但是n的上限为1000W,还不一定有几组数据,这么大的量,直接辗转法循环是肯定会超时的。
一个令人兴奋的消息是,我的同学小明将这道题做出来,并且AC了,我们来看看他的代码:
#include <iostream>
using namespace std;
const int MOD = 7;
int fib[50] = {0, 1, 1};
int a, b, n;
int main()
{
while (cin >> a >> b >> n, a || b || n)
{
int i;
for (i = 3; i <= 49; i++)
{ //最多有49种组合
fib[i] = (a * fib[i - 1] + b * fib[i - 2]) % MOD;
if (fib[i - 1] == 1 && fib[i] == 1) break; //如果提前重复,直接break
}
cout << fib[(n - 1) % (i - 2) + 1] << endl;
}
return 0;
}
我们来分析一下他的代码:
由于fib[n]的值与前两个值fib[n-1]和fib[n-2]有关,那么当前两个值相同的时候,就会出现循环。而结果又是对7取模的,每个数有[0, 6]的7种情况,两个数的组合就是7*7=49种,所以可推出每到49必循环。但这个组合是最大可能,也就是说最多49个数一定有重复的,但还有可能提前重复。所以他根据已知的前两个数来判断是否重复,可以减少一些遍历次数。
这个想法是正确的,但有个地方我当时看了他的代码之后,产生了一些疑问。那就是判断循环的条件。
按他的猜想,我起始是{1, 1},那么如果我再遇到两个连续的1,说明之后的序列,和前面的序列一定是重复的,这看起来并没有什么问题。但谁说循环一定是从头开始循环呢?
我自己调试了一个数据,如下:
A = 2, B = 7;
第0~49项参数为:0 1 1 2 4 1 2 4 1 2 4 1 2 4 1 2 4 1 2 4 1 2 4 1 2 4 1 2 4 1 2 4 1 2 4 1 2 4 1 2 4 1 2 4 1 2 4 1 2 4
所以,这组数据实际上重复的是什么?是{1, 2, 4},而不是小明设想的{1, 1, …}。
这样的话,他这个代码实际上是有bug的,只不过HDoj的评测点有漏洞,恰好过了。希望有机会,官方能够修复一下这个评测漏洞。
但追求完美的我们,是一定不能拿一个假AC来糊弄的,于是我写了以下代码:
主要的思路就是类似尺取法,算出一个数,就看看新出现的这对组合,和前面有没有相同的,如果相同了,说明从前一组到这一组之间,就是真正的循环了。
这里有两点:第一,从第一项开始,到真正的循环序列之间那一段len1,要单独拿出来;第二,两个组合之间的长度len2,才是真正的循环周期,后面求解fib[n]的时候,要对这个周期len2取余。
最终的转换式,就是:
(
n
−
l
e
n
1
)
%
l
e
n
2
+
l
e
n
1
(n-len1) \% len2 + len1
(n−len1)%len2+len1。
代码如下:
#include <iostream>
using namespace std;
const int MOD = 7;
int fib[50] = { 0, 1, 1 };
int a, b, n;
int main()
{
freopen("input.txt", "r", stdin);
freopen("output.txt", "w", stdout);
while (cin >> a >> b >> n, a || b || n)
{
int len1, len2;
bool Next = true;
for (int i = 3; Next; i++)
{ //直到重复为止
fib[i] = (a * fib[i - 1] + b * fib[i - 2]) % MOD;
for (int j = 2; j <= i - 2; j++)
{
if (fib[j] == fib[i] && fib[j - 1] == fib[i - 1])
{
len1 = j - 1;
len2 = i - j;
Next = false;
break; 如果提前重复,直接break
}
}
}
cout << fib[(n - len1) % len2 + len1] << endl;
}
return 0;
}