杭电OJ 1005题 题目链接:点击打开链接
本题思路:给出的公式,第一个想到的是递归,使用递归实现,果然超内存,因为1 <= n <= 100,000,000(嘿嘿,我知道不用试也应该知道);接下来尝试使用栈的方式实现,超时了,至此偷懒不成功,开始寻找更好的方法吧。
最终通过网上各种查找,再加上自己的思考和摸索,理解如下:
(1)很明显f(n)的取值范围为0~6
(2)在A,B给定的情况下,f(n)的值由f(n-1)的值和f(n-2)的值决定
(3)考虑数列的可能值,f(1)=1,f(2)=1,f(3)=*,f(4)=*,f(5)=* ....................f(49)=*,f(50)=*,f(51)=*,f(52)=* ............ 。其中‘*’表示值为0~6之间的任意数。将f(1)f(2)这样某两个数放在一起,称为“数值对”(我自己取得名字,方便称呼而已),那么这个数列就可以构成下列的数值对f(1)f(2)、f(2)f(3)、f(3)f(4)、f(4)f(5)......f(49)f(50)、f(50)f(51)、f(51)f(52)等等。现在看这些数值对m1m2,由于m1、m2的取值范围为0~6,那么m1m2数值对的可能情况一共有49种,即00、01、02、04、05、06、10、11、12、13、14、15、16、20.....64、65、66共49(7*7)种。再看数值对f(1)f(2)、f(2)f(3)、f(3)f(4)、f(4)f(5)......f(49)f(50)、f(50)f(51)、f(51)f(52).....,到f(49)f(50)这里时,刚好是第49个数值对,所以说f(50)f(51)一定会和f(1)f(2)~f(49)f(50)中的某个数值对是相同的(因为数值对的所有可能一共是49个嘛,所以最晚到这个时候已经开始出现重复了,当然也可能从f(50)f(51)之前的某个数值对就已经重复了)。
(4)至此我们可以确定,f(50)f(51)一定会和f(1)f(2)~f(49)f(50)中的某个数值对是相同的,也就是说f(50)会等于f(n),n为0~49之间的某个数,而f(51)等于f(n+1)。因为在A、B确定的情况下,f(n)的值由f(n-1)和f(n-2)确定,那么f(n+2)的值也由f(n+1)和f(n)决定,那么我们就找到了一个循环节,也就是f(n)f(n+1)f(n+2)....和f(50)f(51)f(52)...是相同的序列。而50-n的值即为循环周期。
(5)找到循环周期和循环节之后,问题就可以简化了,假设m是个很大的数,f(m)一定会和f(1)~f(49)中的某个数相等。
(6)看了网上的各种题解之后,我的一些个人理解:我不认为循环周期一定是49,也不认为开始产生循环的那个数值对一定是“11”。列如,在a=10,b=21时,就会发现数列的循环周期是48,最开始的11数值对就只出现了一次,整个数列是从f(2)f(3)开始出现循环的。该情况的测试代码也会在最后给出。
本题AC参考代码:
import java.io.BufferedInputStream;
import java.util.Scanner;
import java.util.Stack;
public class Main {
public static int fun(int a, int b, int n) {
// 使用栈保存中间结果,计算f(n)的值
Stack<Integer> nums = new Stack<Integer>();
nums.push(1);
nums.push(1);
int result = 0;
for (int i = 3; i <= n; i++) {
int f1 = nums.pop();
int f2 = nums.pop();
nums.push(f1);
result = (a * f1 + b * f2) % 7;
nums.push(result);
}
return result;
}
public static void main(String[] args) {
Scanner cin = new Scanner(new BufferedInputStream(System.in));
int a = cin.nextInt();
int b = cin.nextInt();
int n = cin.nextInt();
while (!(a == 0 && b == 0 && n == 0)) {
// 首先计算出f(1)~f(49)的值,用数组nums[]保存
int[] nums = new int[49];
nums[0] = 1;
nums[1] = 1;
for (int i = 2; i < 49; i++) {
nums[i] = fun(a, b, i + 1);
}
// 如果n<=49,直接输出
if (n <= 49) {
System.out.println(nums[n - 1]);
} else {
// 计算f(50)、f(51),寻找循环周期
int f50 = fun(a, b, 50);
int f51 = fun(a, b, 51);
int period = 0;
// 遍历nums[]数组,寻找循环周期。在下列循环中一定会找到循环,因为一定会重复
// 具体原因请看上面的题解部分
for (int i = 0; i < 49; i++) {
int fn = nums[i];
int fn1 = nums[i + 1]; // 这个变量的取名其实是想表示f(n+1)的意思...
if (fn == f50 && fn1 == f51) {
// 找到循环周期
// 从f(i+1)~f(49)即为一个循环节
period = 50 - (i + 1);
break;
}
}
// 开始计算f(n),关键是计算出f(n)=f(?)
int subscript = n % period;
//防止数组下标越界,因为下面有nums[subscript-1]
if(subscript == 0){
subscript = period;
}
int result = nums[subscript-1];
System.out.println(result);
}
a = cin.nextInt();
b = cin.nextInt();
n = cin.nextInt();
}
cin.close();
}
}
标号(6)中所提到的测试程序如下:
import java.util.Stack;
public class Test {
public static int fun(int a, int b, int n){
if(n==1 || n==2){
return 1;
}
//使用栈保存中间结果,计算f(n)的值
Stack<Integer> nums = new Stack<Integer>();
nums.push(1);
nums.push(1);
int result = 0;
for(int i=3; i<=n; i++){
int f1 = nums.pop();
int f2 = nums.pop();
nums.push(f1);
result = (a*f1 + b*f2) % 7;
nums.push(result);
}
return result;
}
public static void main(String[] args) {
int a,b,n;
a = 10;
b = 21;
n = 300;
for(int i=1; i<=n; i++){
int result = fun(a,b,i);
System.out.print("a=" + a+" " + "b=" + b + " ");
System.out.println("f(" + i + ")=" + result);
}
}
}
欢迎评论指正讨论,不喜勿喷。