1. 程式人生 > >數列 題解(NOIP模擬T2)

數列 題解(NOIP模擬T2)

cst namespace pac tmp ans [1] 計算 每次 倍增

此文為博主原創題解,轉載時請通知博主,並把原文鏈接放在正文醒目位置。

【題目描述】

a[1]=a[2]=a[3]=1

a[x]=a[x-3]+a[x-1] (x>3)

a數列的第n項對1000000007(10^9+7)取余的值。

【輸入格式】

第一行一個整數T,表示詢問個數。

以下T行,每行一個正整數n。

【輸出格式】

每行輸出一個非負整數表示答案。

【樣例輸入】

3

6

8

10

【樣例輸出】

4

9

19

【數據範圍】

對於30%的數據 n<=100;

對於60%的數據 n<=2*10^7;

對於100%的數據 T<=100,n<=2*10^9;

分析:

數據範圍到了二十億,顯然不優化是絕對要TLE的(事實表明暴力只有60分)

由於這是一個加和遞推式的形式,所以采用(矩陣)快速冪。

大佬說只要是形似這樣的加和的題都可以用矩陣來解。

下面是AC代碼,註釋已經寫得很詳細了不再多說。矩陣這東西還是應該找人來講,文字敘述起來太困難。

  1 #include<iostream>
  2 #include<cstdio>
  3 #include<cstring>
  4 #include<algorithm>
  5 
  6 using namespace std;
7 const int INF = 0x3f3f3f3f; 8 const int MOD = 1000000007; 9 10 long long s1[10][10],s2[10]; 11 long long tmp[10][10],base[10][10],ans[10][10]; 12 13 long long pow(int b) 14 { 15 for(int i = 1;i <= 4;i ++) 16 { 17 ans[i][i] = 1; 18 for(int j = 1;j <= 4;j ++)
19 base[i][j] = s1[i][j] % MOD; 20 } 21 //初始化ans為單位矩陣,base為構造矩陣 22 //以後每次數列+n時只需*base^n 23 while(b) 24 { 25 //b的值不為零,也就是二進制表示中還有1 26 if(b & 1) 27 { 28 for(int i = 1;i <= 4;i ++) 29 for(int j = 1;j <= 4;++ j) 30 tmp[i][j] = ans[i][j] % MOD; 31 //b&1判斷當前位是否為1,是的話就用tmp存儲ans 32 memset(ans,0,sizeof(ans)); 33 34 for(int k = 1;k <= 4;k ++) 35 for(int i = 1;i <= 4;i ++) 36 for(int j = 1;j <= 4;j ++) 37 ans[i][j] += ((tmp[i][k] % MOD) * (base[k][j] % MOD)); 38 //用tmp與base做矩陣乘法,求出ans 39 } 40 41 for(int i = 1;i <= 4;i ++) 42 for(int j = 1;j <= 4;j ++) 43 tmp[i][j] = base[i][j] % MOD; 44 //用tmp存儲base 45 46 memset(base,0,sizeof(base)); 47 48 49 for(int k = 1;k <= 4;k ++) 50 for(int i = 1;i <= 4;i++) 51 for(int j = 1;j <= 4;j ++) 52 base[i][j] += ((tmp[i][k] % MOD) * (tmp[k][j] % MOD)); 53 //計算出新的base = base * base,實際上是倍增求base 54 b >>= 1; 55 } 56 return ((((ans[1][1]%MOD) * (s2[1]%MOD) % MOD) + ((ans[1][2]%MOD) * (s2[2]%MOD) % MOD))%MOD + (((ans[1][3]%MOD) *(s2[3]%MOD)) % MOD + ((ans[1][4]%MOD) * (s2[4]%MOD)) % MOD)%MOD) % MOD; 57 //返回的是ans與s2的乘積,此時ans為base的n次方,n與題目中的n相統一 58 } 59 int main() 60 { 61 int t,n; 62 scanf("%d",&t); 63 while(t) 64 { 65 scanf("%d",&n); 66 if((n == 1) || (n == 2) || (n == 3)) 67 { 68 printf("1\n"); 69 -- t; 70 continue; 71 } 72 else if(n == 4) 73 { 74 printf("2\n"); 75 -- t; 76 continue; 77 } 78 else if(n == 5) 79 { 80 printf("3\n"); 81 -- t; 82 continue; 83 } 84 memset(base, 0, sizeof(base)); 85 memset(tmp, 0, sizeof(tmp)); 86 memset(ans, 0, sizeof(ans)); 87 88 s2[1] = 3 ;s2[2] = 1;s2[3] = 1;s2[4] = 1; 89 //手算推出計算an需要an-2、an-3、an-4 90 //由上面四個量可以推出an+1、an-1、an-2、an-3 91 s1[1][1] = 1;s1[1][2] = 1;s1[1][3] = 0;s1[1][4] = 0; 92 s1[2][1] = 0;s1[2][2] = 1;s1[2][3] = 0;s1[2][4] = 1; 93 s1[3][1] = 0;s1[3][2] = 1;s1[3][3] = 0;s1[3][4] = 0; 94 s1[4][1] = 0;s1[4][2] = 0;s1[4][3] = 1;s1[4][4] = 0; 95 long long a = pow(n - 5) % MOD; 96 printf("%lld\n",a); 97 -- t; 98 } 99 return 0; 100 }

對於上面88—94行的來歷再說一下。

思維過程:

假定我們已知an,需要求an+1,這時我們還需要知道an-2

用an-2可以求得an-1,這時我們還需要知道an-4

用an-4可以求得an-3,an-3又可以求得an-2

將上述整理成矩陣的形式,大概就是這樣的:

技術分享

左邊是條件,檢驗可知用左邊的四個值可以求得右邊的四個值。

而s1矩陣反映的就是從左邊的值到右邊的值的對應關系,用矩陣乘法計算。

關於矩陣乘法這裏不再贅述,前面有一篇博客說過運算法則(盡管說得非常含糊),因為矩陣的東西確實很難用文字表達。。。

至於第95行,為什麽pow(n-5),是因為左邊的a下標最小是n-4,n-5(其實是n+1-5)與之對應。

數列 題解(NOIP模擬T2)