矩阵乘法的定义应当是众所周知的,称 C = A × B C=A\times B C=A×B 当且仅当 C i k = ∑ j A i j B j k C_{ik}=\sum_{j}A_{ij}B_{jk} Cik=∑jAijBjk 。
加法就是对应的位置相加。具有加法交换律,加法、乘法结合律,乘法对加法的分配律。
n n n 维向量点乘 ( a 0 , a 1 , a 2 , … , a n − 1 ) ⋅ ( b 0 , b 1 , b 2 , … , b n − 1 ) = ∑ i = 0 n − 1 a i b i (a_0,a_1,a_2,\dots,a_{n-1})\cdot (b_0,b_1,b_2,\dots,b_{n-1})=\sum_{i=0}^{n-1}a_i b_i (a0,a1,a2,…,an−1)⋅(b0,b1,b2,…,bn−1)=∑i=0n−1aibi 。
加法也是对应位置相加。具有加法、乘法交换律,加法结合律,点乘对加法的分配率。
为了叙述方便,行向量与列向量相乘均代表向量的点乘。还有,零向量(以及全零的矩阵)记作 o ⇀ \overrightharpoon{\bf{o}} o 。
形如 x n = ∑ i = 1 k a i x n − i x_{n}=\sum_{i=1}^{k}a_{i}x_{n-i} xn=i=1∑kaixn−i
对于一个 k × k k\times k k×k 的矩阵 A A A ,可能存在 k × 1 k\times 1 k×1 的非零列向量 v ⇀ \overrightharpoon{\bf{v}} v ,与一个常数 λ \lambda λ ,满足
A v ⇀ = λ v ⇀ A\overrightharpoon{\bf{v}}=\lambda\overrightharpoon{\bf{v}} Av=λv
然后我们把 v ⇀ \overrightharpoon{\bf{v}} v 叫做特征向量。但是为什么呢?
∑ i = 1 n a x i v i = λ v x \sum_{i=1}^{n}a_{xi}v_i=\lambda v_x i=1∑naxivi=λvx
解方程即可。最后得到的方程式为 λ k = ∑ i = 1 k a i λ k − i \lambda^k=\sum_{i=1}^{k}a_i\lambda^{k-i} λk=i=1∑kaiλk−i
毫无疑问,它有 k k k 个根,但是可能相同。这里的 λ \lambda λ 已经扩大到复数域。
v ⇀ \overrightharpoon{\bf{v}} v 有无数多个。显然,如果 v ⇀ = v 0 ⇀ \overrightharpoon{\bf{v}}=\overrightharpoon{\bf v\rm{_0}} v=v0 是一个解,那么 v ⇀ = γ v 0 ⇀ ( γ ∈ R ) \overrightharpoon{\bf v}=\gamma\overrightharpoon{\bf v\rm _0}(\gamma\in\R) v=γv0(γ∈R) 也是一个解。
无论怎么说,对于某一个 λ \lambda λ ,所有的 v ⇀ \overrightharpoon{\bf v} v 在 n n n 维空间中是共线的。
众所周知,齐次常系数递推式可以使用矩阵乘法快速幂进行优化,复杂度 O ( k 3 log n ) \mathcal O(k^3\log n) O(k3logn) 。
不妨假设转移矩阵为 A A A ,递推方程为标准形式,状态向量 v i ⇀ = ( x i , x i − 1 , x i − 2 , … , x i − k + 1 ) \overrightharpoon{\bf{v}\mathnormal{_i}}=(x_i,x_{i-1},x_{i-2},\dots,x_{i-k+1}) vi=(xi,xi−1,xi−2,…,xi−k+1) 。
我们现在的目标就是找到 λ \lambda λ 与 v ⇀ \overrightharpoon{\bf{v}} v (也就是特征向量),满足 A v ⇀ = λ v ⇀ A\overrightharpoon{\bf{v}}=\lambda\overrightharpoon{\bf{v}} Av=λv
那么我们求出 k k k 个 λ \lambda λ ,记为 λ 0 , λ 1 , λ 2 , … , λ k − 1 \lambda_0,\lambda_1,\lambda_2,\dots,\lambda_{k-1} λ0,λ1,λ2,…,λk−1 。
我们引入一个新的多项式 f ( x ) = ∏ i = 0 k − 1 ( x − λ i ) f(x)=\prod_{i=0}^{k-1}\left(x-\lambda_i\right) f(x)=i=0∏k−1(x−λi)
这个 f ( x ) f(x) f(x) 是无需计算的——它恰好就是特征方程移项后的结果 λ k − ∑ i = 0 k − 1 a k − i λ i \lambda^k-\sum_{i=0}^{k-1}a_{k-i}\lambda^i λk−∑i=0k−1ak−iλi 。
根据多项式取模,可以得到 g ( x ) , r ( x ) g(x),r(x) g(x),r(x),满足 x n − k = g ( x ) f ( x ) + r ( x ) (VIP) x^{n-k}=g(x)f(x)+r(x)\tag{VIP} xn−k=g(x)f(x)+r(x)(VIP)
此处可以使用快速幂,就是一个带取模的快速幂,计算 r ( x ) ≡ x n − k ( m o d f ( x ) ) r(x)\equiv x^{n-k}\pmod{f(x)} r(x)≡xn−k(modf(x))
复杂度是 O ( k log k log n ) \mathcal O(k\log k\log n) O(klogklogn)的,还不算太差。
然后我们试着把 f ( x ) f(x) f(x)的定义域转移到矩阵上。其实很简单,因为矩阵的幂与实数的幂是相似的,可以直接写出 f ( A ) = ∏ i = 0 k − 1 ( A − λ i I ) f(A)=\prod_{i=0}^{k-1}(A-\lambda_iI) f(A)=i=0∏k−1(A−λiI)
其中 I I I是单位矩阵, A A A是一个矩阵。乘法就是矩阵乘法。
如果我们的 A A A取题目中的转移矩阵,那么我们一定有这个结论: f ( A ) = o ⇀ f(A)=\overrightharpoon{\bf o} f(A)=o
注:我现在证明不了……
所以将其带入 ( VIP ) (\text{VIP}) (VIP)式中,得到 A n − k = r ( A ) A^{n-k}=r(A) An−k=r(A)
不妨设 r ( x ) r(x) r(x)的系数为 { b i } ( 0 ≤ i < k ) \{b_i\}(0\le i
注意到 A i v k ⇀ = v k + i ⇀ = ( x k + i , x k + i − 1 , x k + i − 2 , … , x i + 1 ) A^i\overrightharpoon{\bf v\mathnormal{_k}}=\overrightharpoon{\bf v\mathnormal{_{k+i}}}=(x_{k+i},x_{k+i-1},x_{k+i-2},\dots,x_{i+1}) Aivk=vk+i=(xk+i,xk+i−1,xk+i−2,…,xi+1),所以我们实际上要求的就是 r ( A ) v k ⇀ = ∑ i = 0 k − 1 b i v k + i ⇀ r(A)\overrightharpoon{\bf v\mathnormal{_k}}=\sum_{i=0}^{k-1}b_i\overrightharpoon{\bf v\mathnormal{_{k+i}}} r(A)vk=i=0∑k−1bivk+i
所以我们只需要暴力计算出 x k + 1 , x k + 2 , x k + 3 , … , x 2 k x_{k+1},x_{k+2},x_{k+3},\dots,x_{2k} xk+1,xk+2,xk+3,…,x2k,然后直接计算即可。
总时间复杂度是 O ( k 2 + k log k log n ) \mathcal O(k^2+k\log k\log n) O(k2+klogklogn)的。当然,你暴力进行多项式乘法(与取模)也不会太慢的。
传送门 to VJ
#include
#include
#include
using namespace std;
inline int readint(){
int a = 0; char c = getchar(), f = 1;
for(; c<'0' or c>'9'; c=getchar())
if(c == '-') f = -f;
for(; '0'<=c and c<='9'; c=getchar())
a = (a<<3)+(a<<1)+(c^48);
return a*f;
}
const int MaxN = 2005, Mod = 1e9+7;
typedef vector<int> poly;
poly operator * (const poly &a,const poly &b){
poly c; c.resize(a.size()+b.size()-1);
for(int i=0,lena=a.size(); i<lena; ++i)
for(int j=0,lenb=b.size(); j<lenb; ++j)
c[i+j] = (c[i+j]+1ll*a[i]*b[j])%Mod;
return c;
}
poly &operator %= (poly &a,const poly &b){
for(int i=int(a.size())-1,lenb=b.size(); i>=lenb-1; --i){
int f = a[i]/b[lenb-1];
for(int j=lenb; j; --j){
a[i+j-lenb] -= 1ll*b[j-1]*f%Mod;
a[i+j-lenb] = (a[i+j-lenb]+Mod)%Mod;
}
}
while(not a.empty() and a.back() == 0)
a.pop_back(); // 末尾的零
return a;
}
int h[MaxN<<1], a[MaxN], n, k;
void input(){
n = readint(), k = readint();
for(int i=1; i<=k; ++i)
a[i] = (readint()+Mod)%Mod;
for(int i=0; i<k; ++i)
h[i] = (readint()+Mod)%Mod;
}
inline poly qkpow(poly f,int q,poly Mod){
poly ans; ans.push_back(1); // Ans(x) = 1
for(; q; q>>=1,f=f*f,f%=Mod)
if(q&1) ans = ans*f, ans %= Mod;
return ans;
}
void solve(){
for(int i=k; i<=(k<<1); ++i)
for(int j=i-k; j<i; ++j)
h[i] = (h[i]+1ll*h[j]*a[i-j])%Mod;
if(n <= (k<<1)){
printf("%d\n",h[n]);
return ;
}
poly f; f.resize(k+1); // f(x)
for(int i=0; i<k; ++i)
f[i] = (Mod-a[k-i])%Mod;
f[k] = 1; // f(x) = x^k - ...
poly only_x; only_x.resize(2);
only_x[0] = 0, only_x[1] = 1;
f = qkpow(only_x,n-k,f);
int ans = 0;
for(int i=0; i<k; ++i)
ans = (ans+1ll*f[i]*h[i+k])%Mod;
printf("%d\n",ans);
}
int main(){
input(), solve();
return 0;
}