1. 程式人生 > >【bzoj3231】[Sdoi2008]遞歸數列 矩陣乘法+快速冪

【bzoj3231】[Sdoi2008]遞歸數列 矩陣乘法+快速冪

style 其中 std span 處理 轉化 struct set sizeof

題目描述

一個由自然數組成的數列按下式定義: 對於i <= kai = bi 對於i > k: ai = c1ai-1 + c2ai-2 + ... + ckai-k 其中bjcj1<=j<=k)是給定的自然數。寫一個程序,給定自然數m <= n, 計算am + am+1 + am+2 + ... + an, 並輸出它除以給定自然數p的余數的值。

輸入

由四行組成。 第一行是一個自然數k。 第二行包含k個自然數b1, b2,...,bk。 第三行包含k個自然數c1, c2,...,ck。 第四行包含三個自然數m, n, p

輸出

僅包含一行:一個正整數,表示(am
+ am+1 + am+2 + ... + an) mod p的值。

樣例輸入

2
1 1
1 1
2 10 1000003

樣例輸出

142


題解

裸的矩乘快速冪,轉移矩陣都給出來了。

將區間求和轉化為前綴相減處理,對於矩陣[a1 a2 ... ak],按照題目中的公式推出[a2 a3 ... ak+1],然後由於求和,所以需要再開一個位置記錄前綴和。

轉移矩陣自己推一推就好了。

註意特判t<=k的情況。

#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
typedef long long ll;
int n;
ll p , sum[20];
struct data
{
	ll v[20][20];
	data() {memset(v , 0 , sizeof(v));}
	data operator*(const data a)const
	{
		data ans;
		int i , j , k;
		for(i = 1 ; i <= n ; i ++ )
			for(j = 1 ; j <= n ; j ++ )
				for(k = 1 ; k <= n ; k ++ )
					ans.v[i][j] = (ans.v[i][j] + v[i][k] * a.v[k][j]) % p;
		return ans;
	}
	data operator^(const ll a)const
	{
		data x = *this , ans;
		int y = a , i;
		for(i = 1 ; i <= n ; i ++ ) ans.v[i][i] = 1;
		while(y)
		{
			if(y & 1) ans = ans * x;
			x = x * x , y >>= 1;
		}
		return ans;
	}
}B , A;
ll cal(ll t)
{
	if(t < n) return sum[t];
	return (B * (A ^ (t - n + 1))).v[1][n];
}
int main()
{
	int i , j;
	ll l , r;
	scanf("%d" , &n);
	for(i = 1 ; i <= n ; i ++ ) scanf("%lld" , &B.v[1][i]) , B.v[1][n + 1] = B.v[1][n + 1] + B.v[1][i] , sum[i] = sum[i - 1] + B.v[1][i];
	for(i = n ; i >= 1 ; i -- ) scanf("%lld" , &A.v[i][n]) , A.v[i][n + 1] = A.v[i][n];
	for(i = 1 ; i < n ; i ++ ) A.v[i + 1][i] = 1;
	n ++ , A.v[n][n] = 1;
	scanf("%lld%lld%lld" , &l , &r , &p);
	for(i = 1 ; i < n ; i ++ ) sum[i] %= p;
	for(i = 1 ; i <= n ; i ++ )
		for(j = 1 ; j <= n ; j ++ )
			A.v[i][j] %= p , B.v[i][j] %= p;
	printf("%lld\n" , (cal(r) - cal(l - 1) + p) % p);
	return 0;
}

【bzoj3231】[Sdoi2008]遞歸數列 矩陣乘法+快速冪