HDU 1005 Number Sequence

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]=(Afib[i1]+Bfib[i2])%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 (nlen1)%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;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值