问题描述 数列定义如下: f(1)= 1,f(2)= 1,f(n)=(A * f(n-1)+ B * f(n-2))mod 7。 给定A,B和n,你要计算f(n)的值。 输入 输入由多个测试用例组成。 每个测试用例在一行(1 <= A,B <= 1000,1 <= n <= 100,000,000)中包含3个整数A,B和n。三个零表示输入结束,此测试用例不进行处理。 输出 对于每个测试用例,在一行上输出f(n)的值。 样例输入 1 1 3 1 2 10 0 0 0 样例输出 2 5
注意这道题n的范围,1≤n≤1000000000,要小心超时超内存的问题。
看到题目直接写往往会忽视很多细节问题,给出公式的题目注意看看有没有规律。
mod是取模的意思,这里可以认为是取余数。
所谓的同余,顾名思义,就是许多的数被一个数d去除,有相同的余数。d数学上的称谓为模。如a=6,b=1,d=5,则我们说a和b是模d同余的。因为他们都有相同的余数1。 数学上的记法为: a≡ b(mod d) 可以看出当n<d的时候,所有的n都对d同商,比如时钟上的小时数,都小于12,所以小时数都是模12的同商. 对于同余有三种说法都是等价的,分别为: (1) a和b是模d同余的. (2) 存在某个整数n,使得a=b+nd . (3) d整除a-b. 可以通过换算得出上面三个说法都是正确而且是等价的. 基本定律: 同余公式也有许多我们常见的定律,比如相等律,结合律,交换律,传递律….如下面的表示: 1)a≡a(mod d) 2)对称性 a≡b(mod d)→b≡a(mod d) 3)传递性 (a≡b(mod d),b≡c(mod d))→a≡c(mod d) 如果a≡x(mod d),b≡m(mod d),则 4)a+b≡x+m (mod d) 5)a-b≡x-m (mod d) 6)a*b≡x*m (mod d ) 7)a/b≡x/m (mod d) 8)a≡b(mod d)则a-b整除d 9)a≡b(mod d)则a^n≡b^n(mod d) 10)如果ac≡bc(mod m),且c和m互质,则a≡b(mod m) 模运算的运算规则: (a + b) mod p = (a mod p + b mod p) mod p (1) (a - b) mod p = (a mod p - b mod p) mod p (2) (a * b) mod p = (a mod p * b mod p) mod p (3) a^b mod p = ((a mod p)^b) mod p (4) 结合率: ((a+b) mod p + c) mod p = (a + (b+c) mod p) mod p (5) ((a*b) mod p * c) mod p = (a * (b*c) mod p) mod p (6) 交换率: (a + b) mod p = (b+a) mod p (7) (a * b) mod p = (b * a) mod p (8) 分配率: ((a +b) mod p * c) mod p = ((a * c) mod p + (b * c) mod p) mod p (9) 重要定理:若a≡b ( mod p),则对于任意的c,都有(a + c) ≡ (b + c) ( mod p);(10) 若a≡b ( mod p),则对于任意的c,都有(a * c) ≡ (b * c) ( mod p);(11) 若a≡b ( mod p),则对于任意的c,都有ac≡ bc ( mod p); (13) 本文地址:http://blog.csdn.net/a359680405/article/details/41675143
对于公式 f(n) = (A * f(n - 1) + B * f(n - 2)) mod 7,为了防止溢出,改成 f(n) = (A mod 7 * f(n - 1) + B mod 7* f(n - 2))mod 7
(如果看不懂改写原因就看一下上面的了解内容,或者自己写几个例子验证。这题A和B范围比较小,其实不改也可以。)
f(n)自身是保证在0-6的整数范围内的,同样f(n-1)和f(n-2)只有这七种取值。
f(n)是由f(n-1)和f(n-2)确定的,因为A和B是一个确定的值,对整个公式没有影响。
用映射的思想来看,一对f(n-1)和f(n-2)只能映射一个f(n),
所以f(n)的计算 最多 只有 49种可能,如果你还是理解不了,看下面的表格...
假设A=2,B=1...但是循环周期不是49(这是最糟糕的情况)。
fn | f(n-1) | |||||||
0 | 1 | 2 | 3 | 4 | 5 | 6 | ||
f(n-2) | 0 | 0 | 2 | 4 | 6 | 1 | 3 | 5 |
1 | 1 | 3 | 5 | 0 | 2 | 4 | 6 | |
2 | 2 | 4 | 6 | 1 | 3 | 5 | 0 | |
3 | 3 | 5 | 0 | 2 | 4 | 6 | 1 | |
4 | 4 | 6 | 1 | 3 | 5 | 0 | 2 | |
5 | 5 | 0 | 2 | 4 | 6 | 1 | 3 | |
6 | 6 | 1 | 3 | 5 | 0 | 2 | 4 |
如果有连续的两项,在前面的数列中出现过,最小循环节就找到了(注意不一定是出现连续两个1,1的时候,即出现f(1)和f(2))
eg:1 1 3 4 1 6 1 3(1和3在前面出现过了,最小循环节就是13416)
找到了最小循环节,代码就容易写出来了。
下面的写法是直接将49个数作为循环节,但是不是最小循环节(周期函数不是有最小正周期嘛)。
1 #include <iostream> 2 using namespace std; 3 int arr[50]; 4 int main() 5 { 6 int n,a,b; 7 arr[1]=arr[2]=1; 8 while(cin>>a>>b>>n) 9 { 10 if(a==0&&b==0&&n==0) 11 break; 12 int minn=n<50?n:50;//一个小小的优化 13 for(int i=3; i<=minn; i++) 14 { 15 arr[i]=(a*arr[i-1]+b*arr[i-2])%7; 16 } 17 cout<<arr[n%49]<<endl; 18 19 } 20 return 0; 21 }
这个写法比较麻烦,但是也算一种拓展:
1 #include <cstring> 2 #include <cstdio> 3 #include <cstdlib> 4 using namespace std; 5 6 int rec[60]; 7 8 int main() 9 { 10 int a, b, n; 11 rec[0] = rec[1] = rec[2] = 1; 12 while( scanf( "%d %d %d", &a, &b, &n ), a | b | n ) 13 { 14 int beg, end, flag = 0; 15 for( int i = 3; i <= n && !flag; ++i ) 16 { 17 rec[i] = ( a * rec[i-1] + b * rec[i-2] ) % 7; 18 for( int j = 2; j <= i - 1; ++j ) 19 { 20 if( rec[i] == rec[j] && rec[i-1] == rec[j-1] ) 21 { 22 beg = j, end = i; 23 flag = 1; 24 break; 25 } 26 } 27 } 28 if( flag ) 29 { 30 printf( "%d ", rec[beg+(n-end)%(end-beg)] ); 31 } 32 else 33 printf( "%d ", rec[n] ); 34 } 35 return 0; 36 }