杭电1005是以下这样的一道题:
问题描述
一个数字序列定义如下:
f(1)= 1,f(2)= 1,f(n)=(A * f(n-1)+ B * f(n-2))mod 7.
给定A,B和n,您将要计算f(n)的值。
输入项
输入包含多个测试用例。每个测试用例在一行上包含3个整数A,B和n(1 <= A,B <= 1000、1 <= n <= 100,000,000)。三个零表示输入结束,该测试用例将不被处理。
输出量
对于每个测试用例,将f(n)的值打印在一行上。
样本输入
1 1 3
1 2 10
0 0 0
样本输出
2
5
这道题,我这种小白踩了很多坑…
问题解答过程:
我第一眼看过去,就试着用递归解答。
于是我写了以下代码:
递归法
#include<stdio.h>
int main(){
int dg(int a,int b,int n);
int a,b,n,sum;
while(scanf("%d %d %d",&a,&b,&n)!=EOF){
if(a==0&&b==0&&n==0) break;
sum = dg(a,b,n);
printf("%d\n",sum);
}
return 0;
}
int dg(int a,int b,int n){
if(n==1||n==2) return 1;
else
return (a * dg(a,b,n - 1) + b * dg(a,b,n-2)) % 7;
}
}
提交结果当然是没能通过,报的错是Memory Limit Exceeded(空间超出限制)
于是我试着改成用递推法
#include<stdio.h>
int c[100000001];
int main(){
int a,b,n,i;
while(scanf("%d %d %d",&a,&b,&n)!=EOF){
if(a==0&&b==0&&n==0) break;
c[0] = 1;
c[1] = 1;
for(i =2 ;i<n;i++)
c[i] = (a*c[i-1]+b*c[i-2])%7;
printf("%d\n",c[n-1]);
}
return 0;
}
结果可想而知,也是Memory Limit Exceeded(空间超出限制)。我脑子真的秀逗了。递归和递推都是占用大量空间的,何况题目n可以等于100000000那么大。
于是我在一步改进算法:
用递推迭代
#include<stdio.h>
int main(){
int a,b,n,i;
int one,two,three;
while(scanf("%d %d %d",&a,&b,&n)!=EOF){
if(a==0&&b==0&&n==0) break;
one = 1;
two = 1;
for(i =2 ;i<n;i++){
three = (a*two+b*one)%7;
one = two;
two = three;
}
printf("%d\n",two);
}
return 0;
}
结果还是没能accepted,这次是时间超出限制。。。
分析:
1 <= n <= 100,000,000
就是说可能要迭代计算100000000次,超时也正常,f(n)的值也必须依赖f(n-1)和f(n-2),这样还是无论如何都无法避免前面的计算。
那必定不是一步步递推计算得来,应该存在规律。观察发现每次的值都是0-6即{0,1 ,2,3,4,5,6},是否会存在循环呢?如果存在循环就好办,算法时间复杂度可从O(n)变O(1)。
再分析
f(n)=(A * f(n-1)+ B * f(n-2))mod 7
因为A,B是固定的值,f(n-1)和f(n-2)决定f(n)的值,那么只有f(n-1)和f(n-2)的值重复出现一次,那么必定会循环。而f(n-1)和f(n-2)的取值都是0-6,根据组合出来的结果是7*7=49种,也就是说最坏的情况是第50次才重复一次,那最坏周期就是49.
以下是能accepted的代码:
#include<stdio.h>//规律
int main(){
int a,b,n,i;
int flag[50];
while(scanf("%d %d %d",&a,&b,&n)!=EOF){
if(a==0&&b==0&&n==0) break;
flag[1] = 1;
flag[2] = 1;
for(i =3 ;i<=49;i++){
flag[i] = (flag[i-1]*a+flag[i-2]*b)%7;
}
printf("%d\n",flag[n%49]);
}
return 0;
}
我只想到必定存在规律,没找到周期,最后是参考网上大神才知道周期是49,只能说,不容易,脑子不行。