(详解)矩阵快速幂详解与常见转移矩阵的构造_秦小咩的博客-CSDN博客_矩阵快速幂转移矩阵
目录
矩阵乘法
假设有n的地点,从地点i到地点j坐飞机有aij种选择,坐火车有bij种选择。求从地点i先坐飞机再转成火车到j的方案数
矩阵乘法
假设 A=(aij),B=(bij)是两个n元矩阵,它们的乘积C=AB也是一个n元矩阵C=(cij),
满足cij=
矩阵快速幂 伪代码模板
const int N=505;
void matpow()
{
int ans[N][N];
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
ans[i][j]=(i==j);
while(pow)
{
if(pow&1)
matmul(ans,base,ans);
matmul(base,base,base);
}
for(int i=1;i<=n;i++)
{
for(int j=1;j<=n;j++)
{
a[i][j]=ans[i][j];
}
}
}
例题一
Magic Gems |
行 列
考虑继承
base矩阵是快速幂关键,继承也就有一定技巧性
设F[n] = base* F[n-1]则
n-m=1时 n-1=m
F[n]= base ^n-m * F(m)
其中F数组m行 base数组 m行*m列·
# include<cstdio>
# include<algorithm>
# include<iostream>
# include<map>
# include<cstring>
# include<set>
# include<algorithm>
# include<vector>
# include<queue>
# include<cstring>
using namespace std;
# define mod 1000000007
typedef long long int ll;
ll f[110][110],base[110][110];
ll n,m;
void mul(ll a[110][110],ll b[110][110])
{
ll c[110][110]= {0};
for(int i=1; i<=m; i++)
{
for(int j=1; j<=m; j++)
{
for(int k=1; k<=m; k++)
{
c[i][j]=(c[i][j]+a[i][k]*b[k][j]%mod)%mod;
}
}
}
for(int i=1; i<=m; i++)
{
for(int j=1; j<=m; j++)
{
a[i][j]=c[i][j]%mod;
}
}
}
ll ans[110][110];
int main ()
{
cin>>n>>m;
base[1][1]=1;
base[1][m]=1;
for(int i=2; i<=m; i++)
{
base[i][i-1]=1;
}
f[1][1]=2;
for(int i=2; i<=m; i++)
{
f[i][1]=1;
}
ll pow=n-m;
if(pow<=0)
{
if(pow<0)
cout<<1;
else
cout<<2;
return 0;
}
for(int i=1; i<=m; i++)
{
ans[i][i]=1;
}
while(pow)
{
if(pow&1)
mul(ans,base);
pow>>=1;
mul(base,base);
}
ll an=0;
for(int i=1;i<=m;i++)
{
an=(an+ans[1][i]*f[i][1]%mod)%mod;
}
cout<<an;
return 0;
}
例题2
方格填色 |
我们用1代表白色,0代表黑色。左右两格不能同时是白色 &运算之后,一定是0 ,两列不能全是黑也就是两列不能全是0
其中base数组的转移规则为,i,j不能同时是0,且ij&运算之后不能是1
另外base数组的pow应该是n-1
# include<cstdio>
# include<algorithm>
# include<iostream>
# include<map>
# include<cstring>
# include<set>
# include<algorithm>
# include<vector>
# include<queue>
# include<cstring>
using namespace std;
# define mod 1000000007
typedef long long int ll;
ll base[50][50],n,m;
ll f[50][50];
ll ans[50][50];
void cheng(ll a[50][50],ll b[50][50])
{
ll temp[50][50]={0};
for(int i=0;i<(1<<m);i++)
{
for(int j=0;j<(1<<m);j++)
{
for(int k=0;k<(1<<m);k++)
{
temp[i][j]=(temp[i][j]+a[i][k]*b[k][j]%mod)%mod;
}
}
}
for(int i=0;i<(1<<m);i++)
{
for(int j=0;j<(1<<m);j++)
{
a[i][j]=temp[i][j];
}
}
}
int main ()
{
cin>>m>>n;
for(int i=0;i<(1<<m);i++)
{
for(int j=0;j<(1<<m);j++)
{
if(i&j)
continue;
if(!i&&!j)
continue;
base[i][j]=1;
}
}
ll pow=n-1;
for(int i=0;i<(1<<m);i++)
{
ans[i][i]=1;
f[i][1]=1;
}
while(pow)
{
if(pow&1)
cheng(ans,base);
pow>>=1;
cheng(base,base);
}
ll an=0;
for(int i=0;i<(1<<m);i++)
{
for(int j=0;j<(1<<m);j++)
{
an=(an+ans[i][j]*f[j][1]%mod)%mod;
}
}
cout<<an;
return 0;
}
例题三
Sasha and Array |
首先,菲波那切数列的转移方程
在这里不得不提一个规律,那就是base数组的x次幂所得到的 base[1][1]正好是 f(x-1)
比如0次幂的单位矩阵 base[1][1]=1=f(1)
1次幂 base[1][1]=f(2)
2次幂 base[1][1]=f(3)
而本题的突破点就在这里。
每次进行的区间修改用线段树来实现,那么线段树的实际意义该如何定义?
首先看两项操作 对于[L,R]区间内的a[i]进行加 x,操作
对于【L,R]区间内的斐波那契数列求和
第一项操作对于第二项操作的影响在于 每次加x,相当于对f[i]对应的base矩阵进行了幂数+x,也就是乘上一个幂数为x的base矩阵
之后我们线段树的sum数组该如何定义? 我们的lazy标记又该如何定义
首先我们是无法预处理出10^9那么大的斐波那契数列的,也就意味着我们sum数组不能是单纯的数字,应该是矩阵形式。
而sum数组就对应了 该区间base数组之和。 这样的合理性在于 [L,R]之间所有矩阵分别乘x个base矩阵,所得到的base[1][1]之和,与先把矩阵相加,再乘x个base矩阵所得结果相同
在返回答案时,只需要进行base[1][1]的求和即可
涉及重载运算符,代码细节多
# include<iostream>
# include<cstring>
# include<vector>
# include<math.h>
# include<algorithm>
using namespace std;
# define mod 1000000007
typedef long long int ll;
struct MAT
{
ll mat[3][3]={0};
void init()
{
mat[1][1]=mat[2][2]=1;
mat[1][2]=mat[2][1]=0;//这一行必须有,没有就会错,之所以会错,是因为init不仅要承担0矩阵初始化为0级幂矩阵的任务还承担的降幂的任务
}
}sum[100000*4+10],lazy[100000*4+10];
MAT operator*(MAT a, MAT b)
{
MAT c;
memset(c.mat,0,sizeof(c.mat));
for(int i=1;i<=2;i++)
{
for(int j=1;j<=2;j++)
{
for(int k=1;k<=2;k++)
{
c.mat[i][j]=(c.mat[i][j]+a.mat[i][k]*b.mat[k][j]%mod)%mod;
}
}
}
return c;
}
MAT operator+ (MAT a, MAT b)
{
MAT c;
memset(c.mat,0,sizeof(c.mat));
for(int i=1;i<=2;i++)
{
for(int j=1;j<=2;j++)
{
c.mat[i][j]=(a.mat[i][j]+b.mat[i][j])%mod;
}
}
return c;
}
MAT quickpow(ll pow)
{
MAT c;
c.init();
MAT base;
base.mat[1][1]=base.mat[1][2]=base.mat[2][1]=1;
base.mat[2][2]=0;
while(pow)
{
if(pow&1)
c=c*base;
pow>>=1;
base=base*base;
}
return c;
}
void pushup(int rt)
{
sum[rt]=sum[rt<<1]+sum[rt<<1|1];
}
void pushdown(int rt) //懒标记的下传实际上是幂级的累加,幂级的累加靠乘法来实现
{
sum[rt<<1]=sum[rt<<1]*lazy[rt];
sum[rt<<1|1]=sum[rt<<1|1]*lazy[rt];
lazy[rt<<1]=lazy[rt<<1]*lazy[rt];
lazy[rt<<1|1]=lazy[rt<<1|1]*lazy[rt];
lazy[rt].init(); //幂级归零
}
void build(int l,int r,int rt)
{
sum[rt].init();
lazy[rt].init();
if(l==r)
{
ll x;
cin>>x;
sum[rt]=quickpow(x-1); //f(x)为x-1次幂的base数组
return ;
}
int mid=(l+r)>>1;
build(l,mid,rt<<1);
build(mid+1,r,rt<<1|1);
pushup(rt);
}
void change(int L,int R,int l,int r,int rt,MAT x) //把矩阵块累加到区间
{
if(L<=l&&r<=R)
{
lazy[rt]=lazy[rt]*x;
sum[rt]=sum[rt]*x;
return;
}
pushdown(rt);
int mid=(l+r)>>1;
if(L<=mid)
change(L,R,l,mid,rt<<1,x);
if(R>mid)
change(L,R,mid+1,r,rt<<1|1,x);
pushup(rt);
}
ll getsum(int L,int R,int l,int r,int rt)
{
if(L<=l&&r<=R)
{
return sum[rt].mat[1][1]%mod;
}
pushdown(rt);
int mid=(l+r)>>1;
ll ans=0;
if(L<=mid)
ans=(ans+getsum(L,R,l,mid,rt<<1))%mod;
if(R>mid)
ans=(ans+getsum(L,R,mid+1,r,rt<<1|1))%mod;
return ans;
}
int main ()
{
int n,m;
cin>>n>>m;
build(1,n,1);
while(m--)
{
int a;
cin>>a;
if(a==1)
{
ll l,r,x;
cin>>l>>r>>x;
MAT y=quickpow(x);
change(l,r,1,n,1,y);
}
else
{
int l,r;
cin>>l>>r;
cout<<getsum(l,r,1,n,1)<<'\n';
}
}
return 0;
}
例题四
区间加区间sin和 |
根据公式
且易发现,对一个区间每个数加a,再根据公式求sin 与先求sin之和再根据公式求sin结果相同
故可以利用线段树进行求解。懒标记是所加的a,懒标记的下传方式需要按照公式
# include<iostream>
# include<cstring>
# include<vector>
# include<math.h>
# include<algorithm>
using namespace std;
# define mod 1000000007
typedef long long int ll;
double sumsin[200000*4+10],sumcos[200000*4+10],lazy[200000*4+10];
/*
sin(a+x) = sina cosx + sinx cosa
cos(a+x) =cosa cosx -sina sinx
*/
void pushup(int rt)
{
sumcos[rt]=sumcos[rt<<1]+sumcos[rt<<1|1];
sumsin[rt]=sumsin[rt<<1]+sumsin[rt<<1|1];
}
void push(double la,int rt)
{
double temsin=sumsin[rt];
double temcos=sumcos[rt];
sumsin[rt]=sin(la)*temcos+cos(la)*temsin;
sumcos[rt]=temcos*cos(la)-sin(la)*temsin;
}
void pushdown(int rt)
{
if(lazy[rt]) //进行下传
{
push(lazy[rt],rt<<1);
push(lazy[rt],rt<<1|1);
lazy[rt<<1]+=lazy[rt];
lazy[rt<<1|1]+=lazy[rt];
lazy[rt]=0;
}
}
void build(int l,int r,int rt)
{
if(l==r)
{
double x;
cin>>x;
sumsin[rt]=sin(x);
sumcos[rt]=cos(x);
return ;
}
int mid=(l+r)>>1;
build(l,mid,rt<<1);
build(mid+1,r,rt<<1|1);
pushup(rt);
}
void change(int L,int R,int l,int r,int rt,double x)
{
if(L<=l&&r<=R)
{
push(x,rt);
lazy[rt]+=x;
return ;
}
int mid=(l+r)>>1;
pushdown(rt);
if(L<=mid)
change(L,R,l,mid,rt<<1,x);
if(R>mid)
change(L,R,mid+1,r,rt<<1|1,x);
pushup(rt);
}
double querysum(int L,int R,int l,int r,int rt)
{
if(L<=l&&r<=R)
{
return sumsin[rt];
}
int mid=(l+r)>>1;
pushdown(rt);
double ans=0;
if(L<=mid)
ans+=querysum(L,R,l,mid,rt<<1);
if(R>mid)
ans+=querysum(L,R,mid+1,r,rt<<1|1);
return ans;
}
int main ()
{
int n;
cin>>n;
build(1,n,1);
int t;
cin>>t;
while(t--)
{
int a;
cin>>a;
if(a==1)
{
int l,r,x;
cin>>l>>r>>x;
change(l,r,1,n,1,x);
}
else
{
int l,r;
cin>>l>>r;
cout<<querysum(l,r,1,n,1)<<endl;
}
}
return 0;
}
高斯消元
为线性代数在计算机编码中的应用。
常见的形式为求解 线性方程组
ax+by+cz= A
dx+ey+fz=B
gx+hy+iz=C
求解出 x,y,z
名词 系数矩阵
增广矩阵
利用高斯消元可以将此线性方程组进行求解
常见的高斯消元有四大类 整数解型,浮点数解型,取模型,异或型。
整形高斯消元
int a[50][50];
int x[50];
int gcd(int x,int y)
{
return !x?x:gcd(y,x%y);
}
int lcm(int x,int y)
{
return x/gcd(x,y)*y;
}
int Gauss(int hang,int lie)
{
for(int i=0;i<=lie;i++)
{
x[i]=0;
}
int row=0,col=0;
for(row=0;row<hang&&col<lie;col++,row++)
{
int maxrow=row;
for(int i=row+1;i<hang;i++)
{
if(abs(a[i][col])>abs(a[maxrow][col]))
maxrow=i;
}
if(maxrow!=row)
{
for(int j=col;j<=lie;j++)
{
swap(a[row][j],a[maxrow][j]);
}
}
if(a[row][col]==0)
{
row--;
continue;
}
for(int i=row+1;i<hang;i++)
{
if(a[i][col]!=0)
{
int LCM=lcm(abs(a[i][col]),abs(a[row][col]));
int ta=LCM/abs(a[i][col]);
int tb=LCM/abs(a[row][col]);
if(a[i][col]*a[row][col]<0)
tb=-tb;
for(int j=col;j<=lie;j++)
{
a[i][j]=a[i][j]*ta-a[row][j]*tb;
}
}
}
}
for(int i=row;i<hang;i++)
{
if(a[i][col])
return -1; //无解
}
int temp=hang-row;
if(row<hang)
return temp; //自由元
for(int i=hang-1;i>=0;i--)
{
int temp=a[i][lie];
for(int j=i+1;j<lie;j++)
{
if(a[i][j])
{
temp-=a[i][j]*x[j]; //该行j列系数和第j解
}
}
if(temp%a[i][i])
return -2; //有解但无整数解
x[i]=temp/a[i][i];
}
return 0;
}
浮点型高斯消元
例题
细节为EPS的设定
# include<iostream>
# include<cstring>
# include<vector>
# include<iomanip>
# include<queue>
# include<set>
# include<math.h>
# include<algorithm>
using namespace std;
# define mod 998244353
typedef __int128 ll;
double a[110][110];
double x[110];
bool check(double x)
{
return fabs(x)>1e-6;
}
int Gauss(int hang,int lie)
{
for(int i=0;i<=lie;i++)
{
x[i]=0;
}
int row=0,col=0;
for(row=0;row<hang&&col<lie;col++,row++)
{
int maxrow=row;
for(int i=row+1;i<hang;i++)
{
if(fabs(a[i][col])>fabs(a[maxrow][col]))
maxrow=i;
}
if(maxrow!=row)
{
for(int j=col;j<=lie;j++)
{
swap(a[row][j],a[maxrow][j]);
}
}
if(!check(a[row][col]))
{
row--;
continue;
}
for(int i=row+1;i<hang;i++)
{
if(check(a[i][col]))
{
double ji=a[i][col]/a[row][col];
for(int j=col;j<=lie;j++)
{
a[i][j]-=a[row][j]*ji;
}
a[i][col]=0;
}
}
}
for(int i=row;i<hang;i++)
{
if(check(a[i][col]))
return -1; //无解
}
int temp=hang-row;
if(row<hang)
return temp; //自由元
for(int i=hang-1;i>=0;i--)
{
double tem=a[i][lie];
for(int j=i+1;j<lie;j++)
{
if(a[i][j])
{
tem-=a[i][j]*x[j]; //该行j列系数和第j解
}
}
x[i]=tem/a[i][i];
}
return 0;
}
int main()
{
int n;
cin>>n;
for(int i=0;i<n;i++)
{
for(int j=0;j<=n;j++)
{
cin>>a[i][j];
}
}
if(Gauss(n,n))
{
cout<<"No Solution";
}
else
{
for(int i=0;i<n;i++)
{
cout<<fixed<<setprecision(2)<<x[i]<<endl;
}
}
return 0;
}
模数型高斯消元
int a[100][100];
int x[100];
int gcd(int a,int b)
{
return !b?a:gcd(b,a%b);
}
int lcm(int a,int b)
{
return a/gcd(a,b)*b;
}
int gauss(int hang,int lie)
{
for(int i=0;i<=lie;i++)
{
x[i]=0;
}
int col=0,row=0;
for(row=0;row<hang&&col<lie;row++,col++)
{
int maxrow=row;
for(int i=row+1;i<hang;i++)
{
if(abs(a[i][col])>abs(a[maxrow][col]))
maxrow=i;
}
if(maxrow!=row)
{
for(int j=col;j<=lie;j++)
{
swap(a[row][j],a[maxrow][j]);
}
}
if(a[row][col]==0)
{
row--;
continue;
}
for(int i=row+1;i<hang;i++)
{
if(a[i][col]!=0)
{
int lc=lcm(abs(a[i][col]),abs(a[row][col]));
int ta=lc/abs(a[i][col]);
int tb=lc/abs(a[row][col]);
if(a[i][col]*a[row][col]<0)
tb=-tb;
for(int j=col;j<=lie;j++)
{
a[i][j]=((a[i][j]*ta-a[row][j]*tb)%mod+mod)%mod;
}
}
}
}
for(int i=row;i<hang;i++)
{
if(a[i][col])
return -1;
}
int temp=hang-row;
if(row<hang)
return temp;
for(int i=hang-1;i>=0;i--)
{
int temp=a[i][lie];
for(int j=i+1;j<lie;j++)
{
if(a[i][j])
{
temp-=a[i][j]*x[j];
temp=(temp%mod+mod)%mod;
}
}
while(temp%a[i][i])
{
temp+=mod;
}
x[i]=(temp/a[i][i])%mod;
}
return 0;
}
异或型高斯消元
通常其系数矩阵为01,求出的解集也是01的形式
int a[100][100];
int x[100];
int gauss(int hang,int lie)
{
for(int i=0;i<=lie;i++)
{
x[i]=0;
}
int col=0,row=0;
for(row=0;row<hang&&col<lie;row++,col++)
{
int maxrow=row;
for(int i=row+1;i<hang;i++)
{
if(abs(a[i][col])>abs(a[maxrow][col]))
maxrow=i;
}
if(maxrow!=row)
{
for(int j=col;j<=lie;j++)
{
swap(a[row][j],a[maxrow][j]);
}
}
if(a[row][col]==0)
{
row--;
continue;
}
for(int i=row+1;i<hang;i++)
{
if(a[i][col])
{
for(int j=col;j<=lie;j++)
{
a[i][j]^=a[row][j];
}
}
}
}
for(int i=row;i<hang;i++)
{
if(a[i][col])
return -1;
}
int temp=hang-row;
if(row<hang)
return temp;
for(int i=hang-1;i>=0;i--)
{
x[i]=a[i][lie];
for(int j=i+1;j<lie;j++)
{
x[i]^=(a[i][j]&&x[j]);
}
//异或之后x[i]==(a[i][i]&&x[i]) 如果是1,那么x[i]必须是1,如果是0,那么要具体看题意再进行分析
}
return 0;
}
例题
EXTENDED LIGHTS OUT |
典型例题,开关灯问题
以3*3灯为例,研究第一盏灯
L1 ^ ( A(1,1)&&X(1) ) ^ ( A(1,2)&&X(2) ) ^.....( A(1,9)&&X(9) ) =0
L为初始状态 A(x,y)为 第y盏灯的开关是否对第x盏灯有影响 X为这盏灯是否按下
其异或结果为0才能保证最终第一站灯为熄灭状态
而我们对左边异或L1
( A(1,1)&&X(1) ) ^ ( A(1,2)&&X(2) ) ^.....( A(1,9)&&X(9) ) =L1
类似处理其余8个灯,以A为系数矩阵,(A,L)为增广矩阵,求出X即可
# include<iostream>
# include<cstring>
# include<vector>
# include<iomanip>
# include<queue>
# include<set>
# include<math.h>
# include<algorithm>
using namespace std;
# define mod 998244353
typedef __int128 ll;
int getid(int x,int y)
{
return x*6+y;
}
int a[50][50];
int x[50];
int gauss(int hang,int lie)
{
for(int i=0;i<=lie;i++)
{
x[i]=0;
}
int row=0,col=0;
for(row=0;row<hang&&col<lie;row++,col++)
{
int maxrow=row;
for(int i=row+1;i<hang;i++)
{
if(a[i][col]>a[maxrow][col])
{
maxrow=i;
}
}
if(maxrow!=row)
{
for(int j=col;j<=lie;j++)
{
swap(a[row][j],a[maxrow][j]);
}
}
if(a[row][col]==0)
{
row--;
continue;
}
for(int i=row+1;i<hang;i++)
{
if(a[i][col])
{
for(int j=col;j<=lie;j++)
{
a[i][j]^=a[row][j];
}
}
}
}
for(int i=hang-1;i>=0;i--)
{
x[i]=a[i][lie];
for(int j=i+1;j<lie;j++)
{
x[i]^=(a[i][j]&&x[j]);
}
}
return 0;
}
int main()
{
int t;
cin>>t;
int cnt=1;
while(t--)
{
memset(a,0,sizeof(a));
for(int i=0; i<30; i++)
{
cin>>a[i][30];
}
for(int i=0; i<5; i++)
{
for(int j=0; j<6; j++)
{
int t=i*6+j;
a[t][t]=1;
if(i>0)
a[(i-1)*6+j][t]=1;
if(i<4)
a[(i+1)*6+j][t]=1;
if(j>0)
a[i*6+j-1][t]=1;
if(j<5)
a[i*6+j+1][t]=1;
}
}
gauss(30,30);
int mo=5;
cout<<"PUZZLE #"<<cnt<<endl;
for(int i=0;i<30;i++)
{
cout<<x[i]<<" ";
if(i&&i%mo==0)
{
cout<<endl;
mo+=6;
}
}
cnt++;
}
return 0;
}