Description
Cows in the FooLand city are interesting animals. One of their specialties is related to producing offsprings. A cow in FooLand produces its first calve (female calf) at the age of two years and proceeds to produce other calves (one female calf a year).
Now the farmer Harold wants to know how many animals would he have at the end of N years, if we assume that none of the calves die, given that initially, he has only one female calf?
explanation:At the end of 1 year, he will have only 1 cow, at the end of 2 years he will have 2 animals (one parent cow C1 and other baby calf B1 which is the offspring of cow C1).At the end of 3 years, he will have 3 animals (one parent cow C1 and 2 female calves B1 and B2, C1 is the parent of B1 and B2).At the end of 4 years, he will have 5 animals (one parent cow C1, 3 offsprings of C1 i.e. B1, B2, B3 and one offspring of B1).
Input
The first line contains a single integer T denoting the number of test cases. Each line of the test case contains a single integer N as described in the problem.
Output
For each test case print in new line the number of animals expected at the end of N years modulo 10^9 + 7.
Sample Input 1
2
2
4
Sample Output 1
2
5
思路
題目大意:第0年有1只母牛A;第1年有1只母牛A;第2年2只母牛A,B(A生的);第3年3只母牛A,B,C(A生的);第4年5只母牛A,B,C,D(A生的),E(B生的)...
若記第N年末有F(N)只母牛,則有遞推關係F(N) = F(N-1) + F(N-2),其中F(N-1)表示前一年所有的牛(因爲牛不會死亡,所有牛都存活到今年),F(N-2)表示兩年前出生的牛今年開始具備繁殖能力,且每頭生1只牛。
可以看到這其實是一個斐波那契數列問題。
1.簡單的遞歸
n=0或n=1時,F(n) = 1
n>1時,F(n) = F(n-1) + F(n-2)
代碼:
import java.util.Scanner;
public class Main {
/*
*z遞歸法
*z第一年末:一頭牛(A)
*z第二年末:兩頭牛(A,B(A生了B))
*z第三年末:三頭牛(A,B,C(A生了C))
*z第四年末:五頭牛(A,B,C,D(A生了D),E(B生了E))
*z第五年末:八頭牛(A,B,C,D,E,F(A生了F),G(B生了G),H(C生了H))
*z...
*z可以總結出結論:
*T(N) = T(N - 1) + T(N - 2)
*T(N - 1)表示前一年所有牛的數量,因爲第N年時前一年的所有牛都沒死
*T(N - 2)表示第N年時兩年前新出生的牛今年開始具備繁殖能力,從而生出的新牛的數量
*
* */
private static int cal(int n) {
if(n == 0) {
return 1;
} else if(n == 1) {
return 1;
} else {
return cal(n - 1) + cal(n - 2);
}
}
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int cases = Integer.parseInt(sc.nextLine());
for(int turn = 0; turn < cases; turn++) {
int n = Integer.parseInt(sc.nextLine());
int res = cal(n);
System.out.println(res);
}
sc.close();
}
}
但用遞歸超時,遞歸是時間複雜度爲O(n)的解法,斐波那契數列還存在一種O(logn)的解法
2.動態規劃
遞歸慢的原因在於重複計算,導致有很多不必要的開銷。
採用動態規劃,所有值只需要進行一次計算。
斐波那契數列an(n ≥ 0),可以通過矩陣乘法的方式進行遞推:
進而可以得到:
而對於求冪運算,暴力累乘時間複雜度爲O(n),但可以通過遞歸進行優化:
a^n = (a^(n/2))^2 * a^(n%2)
這裏的n/2是取整,如5/2=2。這樣就可以實現相當於二分的效果,時間複雜度爲O(logn)。
代碼:
import java.util.Scanner;
public class Main {
//兩個二維矩陣相乘
private static long[][] multMartix2_2(long[][] m1, long[][] m2) {
long[][] res = new long[2][2];
res[0][0] = m1[0][0] * m2[0][0] + m1[0][1] * m2[1][0];
res[0][1] = m1[0][0] * m2[0][1] + m1[0][1] * m2[1][1];
res[1][0] = m1[1][0] * m2[0][0] + m1[1][1] * m2[1][0];
res[1][1] = m1[1][0] * m2[0][1] + m1[1][1] * m2[1][1];
return res;
}
//1行2列矩陣乘二維矩陣
private static long[] multMatrix1_2(long[] m1, long[][] m2) {
long res[] = new long[2];
res[0] = m1[0] * m2[0][0] + m1[1] * m2[1][0];
res[1] = m1[0] * m2[0][1] + m1[1] * m2[1][1];
return res;
}
//二維矩陣的冪
private static long[][] powMatrix2_2(long[][] m, long n){
long[][] res = new long[2][2];
if(n == 0) {
res[0][0] = res[1][1] = 1;
res[0][1] = res[1][0] = 0;
return res;
} else if(n == 1) {
return m;
} else {
long[][] half = powMatrix2_2(m, n / 2);
return multMartix2_2(multMartix2_2(half, half), powMatrix2_2(m, n % 2));
}
}
private static long fib(long n) {
if(n == 0) {
return 1;
}
if(n == 1) {
return 1;
}
//斐波那契數列的遞推矩陣爲{{0, 1}, {1, 1}}
long[][] m = {{0, 1}, {1, 1}};
//斐波那契數列初始兩值爲1
long[] m0 = {1, 1};
long[][] powRes = powMatrix2_2(m, n - 1);
long[]res = multMatrix1_2(m0, powRes);
return res[1];
}
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int cases = Integer.parseInt(sc.nextLine());
for(int turn = 0; turn < cases; turn++) {
long n = Long.parseLong(sc.nextLine());
long res = fib(n);
System.out.println(res);
}
sc.close();
}
}