题意
f(1)=f(2)=1f(1)=f(2)=1
f(n)=(A∗f(n−1)+B∗f(n−2))mod7f(n)=(A∗f(n−1)+B∗f(n−2))mod7
1≤A,B≤1,000,1≤n≤1081≤A,B≤1,000,1≤n≤108
求f(n)f(n)
思路
看到此题,一种比较平凡的思路就是找规律。很多人的直觉是,循环节的长度必然是49。想到这种想法,主要的理由就是对于一对(A, B)的取值,(f(n),f(n−1))(f(n),f(n−1))的值对最多有49种,所以必然能在50步内找到循环节。由此形成如下代码:
#include <bits/stdc++.h>
using namespace std;
const int N = 50;
int s[N];
int main(){
int a, b, n;
while(scanf("%d%d%d", &a, &b, &n), a+b+n){
s[0] = s[1] = 1;
int T = 49;
for(int i = 2; i < N; i++){
s[i] = (a*s[i-1]+b*s[i-2]) % 7;
if(s[i] == 1 && s[i-1] == 1){
T = i-1;
break;
}
}
printf("%d\n", s[(n+T-1)%T]);
}
return 0;
}
提交后,我们发现这份代码是AC的。但是有一组样例,这样的写法是明显错误的:
此时
这就不符合我们之上的理论了。
我们可以重新思考题意并建立模型,如果我们把任一对相邻的f(x)f(x)值看作一个点即 V={(f(i−1),f(i))}V={(f(i−1),f(i))}那么我们可以知道任一vivi有且仅有一条可能指向自身的边,即f(a,b):vi→vjf(a,b):vi→vj。显然,这是一个49个点组成的图,且每个点有且仅有一条出边。
按直觉我们可以认定这个图必然有环,如果需要严谨证明的话,大概只能用到归纳法了:
若存在有n个点,每个点有一条出边的无环图,那么它必然是由n-1个点的具有同样性质的无环图产生的。(因为一个有环图不能通过增加一个点与一条点上的出边变得无环)
而已知当n=2时,不存在一个2个点,每个点均有一条出边的无环图。
所以有n个点,每个点有一条出边的图必有环。
那么重点即是,这个图中的环并不一定是从头开始,即(1,1)可能并不在循环节中:
知道以上结论后,我们可以将之上的理论进行代码实现:
#include <bits/stdc++.h>
using namespace std;
const int N = 10;
const int M = 50;
int fir[N][N], ans[M];
int main()
{
int a, b, n;
while(scanf("%d%d%d", &a, &b, &n), a+b+n){
int x, y, cnt;
x = y = cnt = 1;
if(n == 1 || n == 2){
printf("1\n");
continue;
}
memset(fir, 0, sizeof(fir));
memset(ans, 0, sizeof(ans));
while(!fir[x][y]){
fir[x][y] = cnt;
ans[cnt++] = y;
int v = (a*y + b*x)%7;
x = y;
y = v;
}
--n;
if(n < fir[x][y]) printf("%d\n", ans[n]);
else{
int loop = cnt - fir[x][y];
n -= fir[x][y];
n %= loop;
printf("%d\n", ans[n+fir[x][y]]);
}
}
return 0;
}
即用fir数组来保存某一个值对第一次出现的计数,并为了我们方便地得到答案,使用ansiansi保存第i个答案。其中fir[x][y]保存的就是循环节的前导部分,loop为循环节长度:
此题还有另外一种解法,即O(logn)O(logn)的矩阵快速幂的算法。
做过用矩阵快速幂求斐波那契数列的第n项的同学会知道,有下式:
即对于An=Tn−1∗AiAn=Tn−1∗Ai,其中
对于二次递推f(n)=A∗f(n−1)+B∗f(n−2)+Cf(n)=A∗f(n−1)+B∗f(n−2)+C,有:
由于此题中C=0C=0,故本题中
代码为:
#include<bits/stdc++.h>
using namespace std;
const int N = 2;
struct Mat{
int a[N][N];
Mat(){memset(a, 0, sizeof(a));}
Mat operator * (Mat& rh){
Mat ret;
for(int i = 0; i < N; i++)
for(int j = 0; j < N; j++)
for(int k = 0; k < N; k++){
ret.a[i][j] += a[i][k] * rh.a[k][j];
ret.a[i][j] %= 7;
}
return ret;
}
};
Mat powMod(Mat a, int e){
Mat ret;
for(int i = 0; i < N; i++) ret.a[i][i] = 1;
for(; e; e>>=1){
if(e&1) ret = ret*a;
a = a*a;
}
return ret;
}
int main()
{
int a, b, n;
while(scanf("%d%d%d", &a, &b, &n), a+b+n){
Mat ans;
ans.a[0][0] = a%7; ans.a[0][1] = b%7; ans.a[1][0] = 1;
if(n<=2){
printf("1\n");
continue;
}
ans = powMod(ans, n-2);
printf("%d\n", (ans.a[0][0]+ans.a[0][1])%7);
}
return 0;
}