マトリックス乗算最適化DP
2741 ワード
久しぶりにブログを更新して、更新して、ついでに自分にも少し熟知させます.noip向上のためにrpを貯める.
マトリクスについて説明します.x*yの縦横2ビットデータテーブルです.例えば、2*2の行列Aは、{(1,2)(3,4)}
ではその中でA(0,1)は2で、このようにして...
最も基本的な行列を紹介した後、行列乗算について話します.マトリクス乗算は2つのマトリクスを乗算します.ただし、マトリクスA*Bなどの制限があります.ここで、Aの大きさがa*bであれば、Bの大きさはb*cでなければならない.
どうしてですか.乗算の過程についてお話しします.A*B=Cの場合、C(i,j)=すべてのA(i,k)*B(k,j)の和.Aの列数とBの行数はいずれもkに等しいので、Aの列数はBの行数に等しいことがわかる.
またA(a*b)*B(b*c)=C(a*c)という例も上の例から容易に見られる.
マトリクス乗算をコードに変換すると容易に実現できる.以下(以下、A(n*t)、B(t*m)の場合):
このような一次行列乗算の時間的複雑さはO(n^3)である.a[i][k]=0の場合、必ず計算しなくてもいいので、加算結果はすべて0なので、このように変更することができます.
そして,行列乗算は結合則のみであり,交換則はない.A*B*C=A*(B*C)ですが、A*B!=B*A.乗算の過程によって考えてみると、なぜか分かります.
結合則があるので,行列は高速べき乗が可能であると述べた.多くの問題を解決することができます.
例題:洛谷1962転送ゲート:https://www.luogu.org/problemnew/show/P1962.
フィボナッチ数列のn番目の項%1億07,000万の値を求めます.nはlonglong範囲内である.
フィボナッチ数列からdp方程式が得られる:f[i]=f[i-1]+f[i-2],nはそんなに大きくて、どうしますか?このとき,各f[i]について,彼の結果はf[i−1]+f[i−2]であり,行列乗算が可能であることが分かった.
行列A(1*2)でシーケンスのi番目とi+1番目を表し,シーケンスのi+1番目とi+2番目をC(1*2)で表す.
では、行列乗算A*B(2*2)=Cに従います.私たちはBを求めなければなりません.
まずC(0,0)=A(0,0)*B(0,0)+A(0,1)*B(1,0)は,C(0,1)がシーケンスのi+1番目の項であり,A(0,1)もシーケンスのi+1番目の項であることから,B(1,0)=1,B(0,0)=0となる.
そして、C(0,1)=A(0,0)*B(0,1)+A(0,1)*B(1,1),C(0,1)はシーケンスのi+2番目の項であり、dp方程式によれば、C(0,1)=A(0,0)+A(0,1)である.したがってB(0,1)=1,B(1,1)=1となる.
こうしてBが出てきたのが、{(0,1)(1,1)}です.初期の2項については,n−1回Bを乗じると最終的なnとn+1項が得られる.しかし,行列には結合則があり,中間は高速べき乗で乗算でき,タイムアウトは起こらない.
このように分析すると、コードが出てきます.
このようなフィボナッチ数列は行列乗算最適化DPにおいて非常に基礎的である.しかしDPの方程式を見つければ,f[i]毎の答えが同じであるという特徴があれば,詳細な解析を経て,ほとんどがこのような行列乗算に変換してDPを最適化することができる.
マトリクスについて説明します.x*yの縦横2ビットデータテーブルです.例えば、2*2の行列Aは、{(1,2)(3,4)}
ではその中でA(0,1)は2で、このようにして...
最も基本的な行列を紹介した後、行列乗算について話します.マトリクス乗算は2つのマトリクスを乗算します.ただし、マトリクスA*Bなどの制限があります.ここで、Aの大きさがa*bであれば、Bの大きさはb*cでなければならない.
どうしてですか.乗算の過程についてお話しします.A*B=Cの場合、C(i,j)=すべてのA(i,k)*B(k,j)の和.Aの列数とBの行数はいずれもkに等しいので、Aの列数はBの行数に等しいことがわかる.
またA(a*b)*B(b*c)=C(a*c)という例も上の例から容易に見られる.
マトリクス乗算をコードに変換すると容易に実現できる.以下(以下、A(n*t)、B(t*m)の場合):
for(int i=0;i
このような一次行列乗算の時間的複雑さはO(n^3)である.a[i][k]=0の場合、必ず計算しなくてもいいので、加算結果はすべて0なので、このように変更することができます.
for(int i=0;i
そして,行列乗算は結合則のみであり,交換則はない.A*B*C=A*(B*C)ですが、A*B!=B*A.乗算の過程によって考えてみると、なぜか分かります.
結合則があるので,行列は高速べき乗が可能であると述べた.多くの問題を解決することができます.
例題:洛谷1962転送ゲート:https://www.luogu.org/problemnew/show/P1962.
フィボナッチ数列のn番目の項%1億07,000万の値を求めます.nはlonglong範囲内である.
フィボナッチ数列からdp方程式が得られる:f[i]=f[i-1]+f[i-2],nはそんなに大きくて、どうしますか?このとき,各f[i]について,彼の結果はf[i−1]+f[i−2]であり,行列乗算が可能であることが分かった.
行列A(1*2)でシーケンスのi番目とi+1番目を表し,シーケンスのi+1番目とi+2番目をC(1*2)で表す.
では、行列乗算A*B(2*2)=Cに従います.私たちはBを求めなければなりません.
まずC(0,0)=A(0,0)*B(0,0)+A(0,1)*B(1,0)は,C(0,1)がシーケンスのi+1番目の項であり,A(0,1)もシーケンスのi+1番目の項であることから,B(1,0)=1,B(0,0)=0となる.
そして、C(0,1)=A(0,0)*B(0,1)+A(0,1)*B(1,1),C(0,1)はシーケンスのi+2番目の項であり、dp方程式によれば、C(0,1)=A(0,0)+A(0,1)である.したがってB(0,1)=1,B(1,1)=1となる.
こうしてBが出てきたのが、{(0,1)(1,1)}です.初期の2項については,n−1回Bを乗じると最終的なnとn+1項が得られる.しかし,行列には結合則があり,中間は高速べき乗で乗算でき,タイムアウトは起こらない.
このように分析すると、コードが出てきます.
#include
using namespace std;
#define mod 1000000007LL
struct matrix{ //
long long a[2][2];
matrix(){
for(int i=0;i<2;i++)
for(int j=0;j<2;j++) a[i][j] = 0LL;
}
}t,d;
matrix operator * (matrix a,matrix b) // , 。
{
matrix c;// 。
for(int i=0;i<2;i++)
for(int j=0;j<2;j++)
for(int k=0;k<2;k++)
c.a[i][j] =(c.a[i][j]+a.a[i][k]*b.a[k][j]%mod)%mod;
return c;
}
matrix qpow(matrix now,long long x) // 。
{
if(x==1LL) return now;
matrix c = qpow(now , x>>1);
if(x&1)return c*c*now;
else return c*c;
}
int main()
{
long long n;
matrix ans;
scanf("%lld",&n);
if(n==1)
{
printf("1");
return 0;
}
t.a[0][0]=t.a[0][1]=d.a[0][1]=d.a[1][1]=d.a[1][0]=1LL; //t ,d B。
ans = t * qpow(d,n-1LL);
printf("%d",ans.a[0][0]%mod);
return 0;
}
このようなフィボナッチ数列は行列乗算最適化DPにおいて非常に基礎的である.しかしDPの方程式を見つければ,f[i]毎の答えが同じであるという特徴があれば,詳細な解析を経て,ほとんどがこのような行列乗算に変換してDPを最適化することができる.