在暑假集训的三校联赛上遇到的题目,
原题网址:http://www.acmusc.cn:9988/problem/246/
原题如下:
521公式
描述
现有公式:F(n) = 5*F(n-1) + 2*F(n-2) +F(n-3) 当n>=3时;因为它的系数分别是5 2 1,所以我们叫它521公式。已知F(0)=1,F(1)=3,F(2)=5。
我们想要知道521公式的区间和模2017的值,也就是说,给定l和r(l<=r),我们要求F(l) + F(l+1) + F(l+2) + … + F(r)取模2017的值。
输入
第一行一个数T,代表有多少组数据。接下来T行,每一行2个数,分别是l和r,输入保证0 <= l <= r <= 1e9。
输出
输出答案取模2017的值,按照”Case %d: “的格式输出。
样例输入
3
0 2
2 5
123 234
样例输出
Case 1: 9
Case 2: 1144
Case 3: 1979
根据该数列的递推公式F(n) = 5*F(n-1) + 2*F(n-2) +F(n-3)不难看出其中一定位置后的元素的数值是巨大的,题目中也提到结果需对2017取余,于是可以得知,题目要求的数列会是一个循环数列,所以这道题可以利用这点进行解答
求循环节代码:
public static void main(String[] args){
Scanner sc = new Scanner(System.in);
int a[] = new int [10000005];//目标数列
a[0] = 1;
a[1] = 3;
a[2] = 5;
for(int i = 3;i <= 10000000;i++){
a[i] = (5*a[i-1] + 2*a[i-2] + 1*a[i-3]) % 2017;
if(a[i] == 5 && a[i-1] == 3 && a[i-2] == 1)
System.out.println(a[i-2] + " " + (i-2));//检测到进入循环的位置时输出该数值及项数
}
}
得出结果为:
1 1356769
1 2713538
1 4070307
1 5427076
1 6783845
1 8140614
1 9497383
可以看出,每组结果的项数差均为:1356769,即循环节
对上面代码稍作处理,可求得一次循环元素值总和对2017取余后为0,到这里,问题就解决了大部分了
对问题提交的代码如下:
import java.util.Scanner;
public class Main{
public static void main(String[] args){
Scanner sc = new Scanner(System.in);
long a[] = new long [1356770];
a[0] = 1;
a[1] = 3;
a[2] = 5;
for(int i = 3;i <= 1356769;i++){
a[i] = (5*a[i-1] + 2*a[i-2] + a[i-3])%2017;
}
int t = sc.nextInt();
int count = 1;//测试数据的序号
while(t-- > 0){
int s = sc.nextInt();//区间起点
int e = sc.nextInt();//区间终点
long sum = 0;//总和
if((e-s) >= 1356768){
e = (e-s) % 1356769;
s = s % 1356769;
e += s;
}//当区间长度超过一次循环时进行消除
s = s % 1356769;
e = e % 1356769;
//将起始位置与终止位置保证在0-1356768间
if((e-s) < 1356768)
for(int i = s;i <= e;i++)
sum = (sum + a[i]) % 2017;
System.out.println("Case "+count+": "+sum);
++count;
}
}
}
找循环节的方法可以适用于一个由不变的公式推算出的并对结果进行了取余的递推数列