【6】矩阵学习笔记
前言
矩阵在 OI 中运用广泛,是一个很重要的内容。正是因为我不会矩阵,所以 WC2024 时讲动态 DP 我没办法听,所以矩阵真的很重要。
由于我实力有限,这里只能介绍一些矩阵的基本应用。
矩阵定义
矩阵:将 个数排列成 行 列的形式称为一个 的矩阵。
矩阵一般用大写字母 来表示,矩阵里第 行第 列的元素用对应的小写字母 来表示。
例如:在矩阵 中,。
方阵:行数与列数相等的矩阵称为方阵。
例如:矩阵 为方阵。
单位矩阵:对于方阵 ,如果主对角线每一个元素均为 ,其余元素为 ,这个矩阵被称为单位矩阵,记作 。
在代码编写中,我们把矩阵定义为一个结构体,这样方便写在函数中作为参数或返回值。矩阵结构体包含矩阵的二维数组,有时包含矩阵的行数和列数。
struct matrix
{
long long n,m;
long long v[200][200];
};
矩阵运算
矩阵加减
对于两个 的矩阵,它们的和为一个 矩阵,矩阵中各位元素等于对应元素之和。
对于两个 的矩阵,它们的差为一个 矩阵,矩阵中各位元素等于对应元素之差。
例:
矩阵加减法满足实数加减法的性质,比如加法交换律。
矩阵乘法
对于一个 的矩阵 和一个 的矩阵 ,它们的积为一个 的矩阵 。在这个矩阵中, 满足如下式子:
例:
矩阵乘法可以简单记为第 行第 列的元素等于第一个矩阵的第 行与第二个矩阵的第 列对应元素相乘的积的和。即先横着看,再竖着看。
矩阵乘法满足结合律,但是不满足交换律,这一点需要特别注意。
单位矩阵 的性质:单位矩阵乘以任何一个矩阵(可以乘的),积为这个矩阵。单位矩阵在矩阵乘法中,就相当于实数乘法中的 。
在代码编写中,我们按照矩阵乘法的定义求出每一个 。这个过程时间复杂度较高,为 ,其中 为矩阵的边长(假设 同阶)。
代码中 为方阵边长。如果是矩阵,把循环中的 n
改为对应的 a.n
,a.m
,b.n
,b.m
即可。
struct matrix mul(struct matrix a,struct matrix b)
{
struct matrix c;
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
c.v[i][j]=0;
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
for(int k=1;k<=n;k++)
c.v[i][j]=c.v[i][j]+a.v[i][k]*b.v[k][j];
return c;
}
矩阵的幂
对于一个方阵 ,记这个方阵的幂为 ,表示 个 连乘的积。
特别的,有 ,其中 为单位矩阵。
我们都知道,可以用快速幂来求一个实数的 次方,那我们也可以用类似的思想来求矩阵的 次方,这就是矩阵快速幂。
相较于快速幂模板,矩阵快速幂只需要把实数改为矩阵,把实数运算改为矩阵运算,并将 的初始值设为单位矩阵 即可。
时间复杂度为 。
代码中 为方阵边长。
struct matrix power(struct matrix a,long long p)
{
struct matrix ans,x=a;
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
{
if(i!=j)ans.v[i][j]=0;
else ans.v[i][j]=1;
}
while(p)
{
if(p&1)ans=mul(ans,x);
p>>=1;
x=mul(x,x);
}
return ans;
}
矩阵加速递推
矩阵可以用来表示递推关系。当用矩阵表示递推关系时,我们把状态写入一个 的矩阵,这个矩阵称为状态矩阵;用一个 的方阵来表示递推关系,这个方阵称为转移矩阵。
由于矩阵乘法独特的计算方式,我们可以通过转移矩阵里各位置的值来确定状态转移时的系数。当我们转移不需要某一个值时,就把这一位设为 。
例如:用矩阵表示 。
首先,我们先把状态写入一个 的矩阵,并在等式另一边写上递推一次后的状态矩阵。这个递推式直接出现的状态为 ,额外会用到的状态为 。我们把这些写入矩阵,并在等式另一边写出每个元素递推一次后的矩阵。
接下来,我们考虑构造中间的转移矩阵。设状态矩阵的第一行为 ,根据矩阵乘法的运算法则,得到如下式子:
因为等号左边均为 ,我们把这个式子和题目所给的递推式进行对比:
观察系数不难发现 。所以我们可以填出转移矩阵的第一行:
接下来,设状态矩阵的第二行为 ,根据矩阵乘法的运算法则,得到如下式子:
很显然, 时就有 ,所以我们可以填出转移矩阵的第二行:
设状态矩阵的第三行为 ,根据矩阵乘法的运算法则,得到如下式子:
同第二行, 时就有 ,所以我们可以填出转移矩阵的第三行:
这样,我们就成功用矩阵表示了 这个递推关系。
这么做的好处是,我们可以用矩阵快速幂在 时间内推出 的值。我们可以简单推导一下:
使用矩阵快速幂,求出 ,再乘以初始状态就能在状态矩阵中推出 。由于转移矩阵很小,矩阵乘法视为常数,最后总的时间复杂度为 。
高斯消元法
我们可以用矩阵表示方程组,例如这个例子:
我们只把各项未知数的系数写进矩阵,就得到了这个方程的矩阵表示。例如上面这个方程组,就可以这样表示:
有的时候我们把 也写入这个矩阵,我们把这样的矩阵称为增广矩阵。
我们可以使用高斯消元法来求出这个方程的解。高斯消元法具体步骤如下:
假设现在处理到第 行。
:在第 行的第 列寻找绝对值最大值,并将绝对值最大值所在的行与第 行交换。这么做是为了避免在第 行 的系数为 导致的一些奇怪情况。由于前 行已经处理了,化成了最终需要的形式,所以不能交换。
:化 的系数为 ,把第 行的每一个元素(包括 )除以 。
:消元。我们希望第 行 的系数为 ,所以我们用加减消元的方式进行消元,具体操作见代码。
根据第 步,从第 行开始, 的系数均为 。所以最后矩阵一定是这个形式,我们称之为上三角形式。
在这个矩阵中,直接从 不断代入就可以求出每一个未知数的值。
时间复杂度为 。
代码中有一些循环的范围容易记错,需要特别注意。另外,如果某次找到的最大值为 ,那么证明这个方程组并不是唯一解,可能是无解或者无穷解。因为如果找到的最大值为 ,第 行的方程就不会被用上(不然你怎么化系数为 )。根据初中数学,一个 元方程组必须至少有 个方程才有唯一解,所以这个方程组必定不是唯一解。
bool gauss()
{
double div=0;
for(int i=1;i<=n;i++)
{
long long mx=i;
for(int j=i+1;j<=n;j++)
if(fabs(a[j][i])>fabs(a[mx][i]))mx=j;
if(fabs(a[mx][i])<eps)return 0;
if(i!=mx)
{
for(int j=1;j<=n;j++)swap(a[i][j],a[mx][j]);
swap(b[i],b[mx]);
}
div=a[i][i],b[i]/=div;
for(int j=1;j<=n;j++)a[i][j]/=div;
for(int j=i+1;j<=n;j++)
{
div=a[j][i],b[j]-=b[i]*div;
for(int k=i;k<=n;k++)a[j][k]-=a[i][k]*div;
}
}
ans[n]=b[n];
for(int i=n-1;i>=1;i--)
{
ans[i]=b[i];
for(int j=n;j>i;j--)ans[i]-=a[i][j]*ans[j];
}
return 1;
}
对于高斯消元判定方程组解的情况,具体请看例题 。
这是更具有普适性的高斯消元算法模板。
long long gauss()
{
double div=0;
for(int i=1;i<=n;i++)
{
long long mx=now;
for(int j=now+1;j<=n;j++)
if(fabs(a[j][i])>fabs(a[mx][i]))mx=j;
if(fabs(a[mx][i])<eps)continue;
if(now!=mx)
{
for(int j=1;j<=n;j++)swap(a[now][j],a[mx][j]);
swap(b[now],b[mx]);
}
div=a[now][i],b[now]/=div;
for(int j=1;j<=n;j++)a[now][j]/=div;
for(int j=now+1;j<=n;j++)
{
div=a[j][i],b[j]-=b[now]*div;
for(int k=i;k<=n;k++)a[j][k]-=a[now][k]*div;
}
now++;
}
if(now-1==n)
{
ans[n]=b[n];
for(int i=n-1;i>=1;i--)
{
ans[i]=b[i];
for(int j=n;j>i;j--)ans[i]-=a[i][j]*ans[j];
}
}
else
{
for(int i=now;i<=n;i++)
if(fabs(b[i])>=eps)return -1;
return 1;
}
return 0;
}
例题
例题 :
矩阵快速幂模板题,不多赘述。
#include <bits/stdc++.h>
using namespace std;
struct matrix
{
long long v[200][200];
}a;
long long n,k,mod=1000000007;
struct matrix mul(struct matrix a,struct matrix b)
{
struct matrix c;
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
c.v[i][j]=0;
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
for(int k=1;k<=n;k++)
c.v[i][j]=(c.v[i][j]+a.v[i][k]*b.v[k][j])%mod;
return c;
}
struct matrix power(struct matrix a,long long p)
{
struct matrix ans,x=a;
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
{
if(i!=j)ans.v[i][j]=0;
else ans.v[i][j]=1;
}
while(p)
{
if(p&1)ans=mul(ans,x);
p>>=1;
x=mul(x,x);
}
return ans;
}
int main()
{
scanf("%lld%lld",&n,&k);
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
scanf("%lld",&a.v[i][j]);
a=power(a,k);
for(int i=1;i<=n;i++)
{
for(int j=1;j<=n;j++)
printf("%lld ",a.v[i][j]);
printf("\n");
}
return 0;
}
例题 :
递推需要用到了量有 ,把它们写入状态矩阵。
对于每一个量,列出递推关系:
把这些递推关系逐行利用待定系数法写入矩阵,得到用矩阵表示的递推关系:
初始状态 ,使用矩阵快速幂递推 次,就可以推出 。
注意 mul(power(a,n-2),ans)
不能写成 mul(ans,power(a,n-2))
,因为矩阵乘法不满足交换律。也要注意 时的特判,否则快速幂会进入死循环。
#include <bits/stdc++.h>
using namespace std;
struct matrix
{
long long v[200][200];
}a,ans;
long long n,k,mod=1000000007;
struct matrix mul(struct matrix a,struct matrix b)
{
struct matrix c;
for(int i=1;i<=2;i++)
for(int j=1;j<=2;j++)
c.v[i][j]=0;
for(int i=1;i<=2;i++)
for(int j=1;j<=2;j++)
for(int k=1;k<=2;k++)
c.v[i][j]=(c.v[i][j]+a.v[i][k]*b.v[k][j])%mod;
return c;
}
struct matrix power(struct matrix a,long long p)
{
struct matrix ans,x=a;
for(int i=1;i<=2;i++)
for(int j=1;j<=2;j++)
{
if(i!=j)ans.v[i][j]=0;
else ans.v[i][j]=1;
}
while(p)
{
if(p&1)ans=mul(ans,x);
p>>=1;
x=mul(x,x);
}
return ans;
}
int main()
{
scanf("%lld",&n);
ans.v[1][1]=1,ans.v[2][1]=1;
a.v[1][1]=1,a.v[1][2]=1,a.v[2][1]=1,a.v[2][2]=0;
if(n>1)
{
ans=mul(power(a,n-2),ans);
printf("%lld",ans.v[1][1]%mod);
}
else printf("1\n");
return 0;
}
例题 :
我认为这题是变相大模拟。递推式比较复杂,梳理一下,用到的量有 共 个,把它们写入状态矩阵。
这里状态矩阵中没有使用 ,是因为 通过改变系数可以做到一样的效果。两种写法都可以。
对于每一个量,列出递推关系:
把这些递推关系逐行利用待定系数法写入矩阵,得到用矩阵表示的递推关系:
根据题意,写出 时等式右边的初始状态。使用矩阵快速幂递推 次,就可以推出 。
注意,在递推中有常数时,要把这个常数也写入矩阵状态。因为递推时所有信息都得包含在转移矩阵中,常数也不例外。
由于模数很大,需要使用龟速乘。总体时间复杂度为 。
#include <bits/stdc++.h>
using namespace std;
struct matrix
{
long long v[200][200];
}a,ans;
long long n,k,p,q,r,t,u,v,w,x,y,z,mod;
void init()
{
ans.v[1][1]=1,ans.v[2][1]=3,ans.v[3][1]=1,ans.v[4][1]=1,ans.v[5][1]=1,ans.v[6][1]=1;
ans.v[7][1]=3,ans.v[8][1]=1,ans.v[9][1]=1,ans.v[10][1]=3,ans.v[11][1]=1;
a.v[1][2]=1,a.v[2][1]=q,a.v[2][2]=p,a.v[2][3]=r,a.v[2][4]=t,a.v[2][5]=1,a.v[2][7]=1;
a.v[2][10]=1,a.v[3][3]=1,a.v[3][4]=2,a.v[3][5]=1,a.v[4][4]=1,a.v[4][5]=1,a.v[5][5]=1;
a.v[6][7]=1,a.v[7][2]=1,a.v[7][6]=v,a.v[7][7]=u,a.v[7][8]=w,a.v[7][10]=1,a.v[8][8]=w;
a.v[9][10]=1,a.v[10][2]=1,a.v[10][4]=1,a.v[10][5]=2,a.v[10][7]=1,a.v[10][9]=y,a.v[10][10]=x;
a.v[10][11]=z,a.v[11][11]=z;
}
long long slow(long long a,long long p)
{
long long ans=0,x=a;
while(p)
{
if(p&1)ans=(ans+x)%mod;
p>>=1;
x=(x+x)%mod;
}
return ans;
}
struct matrix mul(struct matrix a,struct matrix b)
{
struct matrix c;
for(int i=1;i<=11;i++)
for(int j=1;j<=11;j++)
c.v[i][j]=0;
for(int i=1;i<=11;i++)
for(int j=1;j<=11;j++)
for(int k=1;k<=11;k++)
c.v[i][j]=(c.v[i][j]+slow(a.v[i][k],b.v[k][j]))%mod;
return c;
}
struct matrix power(struct matrix a,long long p)
{
struct matrix ans,x=a;
for(int i=1;i<=11;i++)
for(int j=1;j<=11;j++)
{
if(i!=j)ans.v[i][j]=0;
else ans.v[i][j]=1;
}
while(p)
{
if(p&1)ans=mul(ans,x);
p>>=1;
x=mul(x,x);
}
return ans;
}
int main()
{
scanf("%lld%lld",&n,&mod);
scanf("%lld%lld%lld%lld",&p,&q,&r,&t);
scanf("%lld%lld%lld",&u,&v,&w);
scanf("%lld%lld%lld",&x,&y,&z);
init();
ans=mul(power(a,n-1),ans);
printf("nodgd %lld\nCiocio %lld\nNicole %lld\n",ans.v[1][1],ans.v[6][1],ans.v[9][1]);
return 0;
}
例题 :
高斯消元模板题,不多赘述。
#include <bits/stdc++.h>
using namespace std;
long long n;
double a[200][200],b[200],ans[200],eps=1e-10;
bool gauss()
{
double div=0;
for(int i=1;i<=n;i++)
{
long long mx=i;
for(int j=i+1;j<=n;j++)
if(fabs(a[j][i])>fabs(a[mx][i]))mx=j;
if(fabs(a[mx][i])<eps)return 0;
if(i!=mx)
{
for(int j=1;j<=n;j++)swap(a[i][j],a[mx][j]);
swap(b[i],b[mx]);
}
div=a[i][i],b[i]/=div;
for(int j=1;j<=n;j++)a[i][j]/=div;
for(int j=i+1;j<=n;j++)
{
div=a[j][i],b[j]-=b[i]*div;
for(int k=i;k<=n;k++)a[j][k]-=a[i][k]*div;
}
}
ans[n]=b[n];
for(int i=n-1;i>=1;i--)
{
ans[i]=b[i];
for(int j=n;j>i;j--)ans[i]-=a[i][j]*ans[j];
}
return 1;
}
int main()
{
scanf("%lld",&n);
for(int i=1;i<=n;i++)
{
for(int j=1;j<=n;j++)
scanf("%lf",&a[i][j]);
scanf("%lf",&b[i]);
}
if(gauss())
{
for(int i=1;i<=n;i++)
printf("%.2lf\n",ans[i]);
}
else printf("No Solution\n");
return 0;
}
例题 :
由于每次找到的最大值可能为 ,所以我们分别记录目前处理的未知数 和目前用到第 行的方程。如果找到的绝对值最大值为 ,那么我们跳过这个元素,而不是跳过这一行。具体而言,就是把代码中用到 作为行数时直接换成 ,并在每一次成功操作完一行之后将 的值增加。
如果最后我们用到了所有方程,也就是 ,那么这个方程组有唯一解。注意这里是 ,因为 表示的是处理到,但是还没有处理。
否则,这个方程组必定不是唯一解。对于没有用到的方程 中未知数 的系数 ,要么整个第 列绝对值最大值为 ,要么被消元消成 ,要么被换走再消元消成 ,所以这个方程的未知数系数一定全为 。
我们遍历未被处理的第 个方程,根据上文,这个方程的未知数系数一定全为 ,所以等号左边一定为 。如果 ,就是要求 ,这个方程组显然无解。否则,方程组有解,且由于没有用足 个方程,一定无法确定某些未知数,所以方程组有无穷解。
#include <bits/stdc++.h>
using namespace std;
long long n,now=1;
double a[200][200],b[200],ans[200],eps=1e-10;
long long gauss()
{
double div=0;
for(int i=1;i<=n;i++)
{
long long mx=now;
for(int j=now+1;j<=n;j++)
if(fabs(a[j][i])>fabs(a[mx][i]))mx=j;
if(fabs(a[mx][i])<eps)continue;
if(now!=mx)
{
for(int j=1;j<=n;j++)swap(a[now][j],a[mx][j]);
swap(b[now],b[mx]);
}
div=a[now][i],b[now]/=div;
for(int j=1;j<=n;j++)a[now][j]/=div;
for(int j=now+1;j<=n;j++)
{
div=a[j][i],b[j]-=b[now]*div;
for(int k=i;k<=n;k++)a[j][k]-=a[now][k]*div;
}
now++;
}
if(now-1==n)
{
ans[n]=b[n];
for(int i=n-1;i>=1;i--)
{
ans[i]=b[i];
for(int j=n;j>i;j--)ans[i]-=a[i][j]*ans[j];
}
}
else
{
for(int i=now;i<=n;i++)
if(fabs(b[i])>=eps)return -1;
return 1;
}
return 0;
}
int main()
{
scanf("%lld",&n);
for(int i=1;i<=n;i++)
{
for(int j=1;j<=n;j++)
scanf("%lf",&a[i][j]);
scanf("%lf",&b[i]);
}
long long flag=gauss();
if(flag==0)
{
for(int i=1;i<=n;i++)
printf("x%d=%.2lf\n",i,ans[i]);
}
else if(flag==1)printf("0\n");
else printf("-1\n");
return 0;
}
例题 :
设这个球的球心坐标为 ,根据球心到球面上任意一点距离都相等的,我们可以列出一些等量关系式,组成一个方程组。
以第一个坐标 和第二个坐标为 为例,有如下式子:
两边同时平方得:
把平方展开得:
移项合并得:
这样,就产生了一个关于 的方程,且未知数的系数和等号右边的数都已知。对于任意两个的点的坐标,我们都可以这样写出一个方程。只要我们写出至少 个这样的方程联立,就可以用高斯消元法求出每一个未知数,进而确定球心的坐标。
为了方便,对于第 个点,我们都利用这个点和第 个点到球心的距离建立方程。这样我们就可以在输入时记录上一次输入的值与平方和,简便地建立方程。一共输入 个点,这样刚好建立了 个方程。
#include <bits/stdc++.h>
using namespace std;
long long n;
double a[200][200],b[200],ans[200],l[200],now[200],ls,ns,eps=1e-10;
bool gauss()
{
double div=0;
for(int i=1;i<=n;i++)
{
long long mx=i;
for(int j=i+1;j<=n;j++)
if(fabs(a[j][i])>fabs(a[mx][i]))mx=j;
if(fabs(a[mx][i])<eps)return 0;
if(i!=mx)
{
for(int j=1;j<=n;j++)swap(a[i][j],a[mx][j]);
swap(b[i],b[mx]);
}
div=a[i][i],b[i]/=div;
for(int j=1;j<=n;j++)a[i][j]/=div;
for(int j=i+1;j<=n;j++)
{
div=a[j][i],b[j]-=b[i]*div;
for(int k=i;k<=n;k++)a[j][k]-=a[i][k]*div;
}
}
ans[n]=b[n];
for(int i=n-1;i>=1;i--)
{
ans[i]=b[i];
for(int j=n;j>i;j--)ans[i]-=a[i][j]*ans[j];
}
return 1;
}
int main()
{
scanf("%lld",&n);
for(int i=1;i<=n+1;i++)
{
ns=0;
for(int j=1;j<=n;j++)
{
scanf("%lf",&now[j]);
ns+=now[j]*now[j];
if(i!=1)a[i-1][j]=2*(l[j]-now[j]);
}
if(i!=1)b[i-1]=ls-ns;
for(int j=1;j<=n;j++)l[j]=now[j];
ls=ns;
}
gauss();
for(int i=1;i<=n;i++)
printf("%.3lf ",ans[i]);
return 0;
}
例题 :
我们发现,如果两项颜色相同,则把两项都删去,这很符合异或的性质。再结合后面一条两项颜色不同,将这两项替换为与这两种颜色不同的颜色,我们发现需要找到三个数 来代表三种颜色,并且满足 。当 时,刚好满足条件。自然, 就代表了白色与空位。
接下来,我们考虑如何维护另外三种操作。每一个位置的颜色需要两个二进制位来存储,我们把每一个位置的两个二进制位拆成 和 ,其中 表示低位, 表示高位。我们令 对应红色, 对应黄色, 对应蓝色。
对于 RY
操作,我们只需要把子序列内 和 交换,就把所有 R
变为 Y
,所有 Y
变为 R
,B
和空位置不变。
修改前颜色 | 修改前编号 | 修改后编号 | 修改后颜色 |
---|---|---|---|
R |
Y |
||
Y |
R |
||
B |
B |
||
. |
. |
对于 RB
操作,我们只需要把子序列内 改为 ,就把所有 R
变为 B
,所有 B
变为 R
,Y
和空位置不变。
修改前颜色 | 修改前编号 | 修改后编号 | 修改后颜色 |
---|---|---|---|
R |
B |
||
Y |
Y |
||
B |
R |
||
. |
. |
对于 YB
操作,我们只需要把子序列内 改为 ,就把所有 Y
变为 B
,所有 B
变为 Y
,R
和空位置不变。
修改前颜色 | 修改前编号 | 修改后编号 | 修改后颜色 |
---|---|---|---|
R |
R |
||
Y |
B |
||
B |
Y |
||
. |
. |
我们需要对于每个位置维护两个变量, 和 ,分别表示低位是 中的哪一个和高位是 中的哪一个。我们利用位运算,来记录构成这一位的答案中是否存在 或 。
对于每一个 mix
操作,把子序列中的低位和高位依次异或,结果要等于给出的混合结果对应的编号的低位和高位,我们就能得到两个等式。于是,我们就得到了一个异或方程组。直接使用高斯消元算法求解即可。
#include <bits/stdc++.h>
using namespace std;
long long n,k,now[4100],y[4100],sa[4100],sb[4100],cnt=0;
bool a[4100][4100],b[4100],ans[4100];
string op;
void ry()
{
long long m=0,now=0;
cin>>m;
for(int i=1;i<=m;i++)
{
cin>>now;
swap(sa[now],sb[now]);
}
}
void rb()
{
long long m=0,now=0;
cin>>m;
for(int i=1;i<=m;i++)
{
cin>>now;
sb[now]=sa[now]^sb[now];
}
}
void yb()
{
long long m=0,now=0;
cin>>m;
for(int i=1;i<=m;i++)
{
cin>>now;
sa[now]=sa[now]^sb[now];
}
}
void mix()
{
long long m=0,id=0;
char col=0;
cin>>m;
for(int i=1;i<=m;i++)cin>>now[i];
cin>>col;
if(col=='W')id=0;
if(col=='R')id=1;
if(col=='Y')id=2;
if(col=='B')id=3;
cnt++,b[cnt]=id&1;
for(int i=1;i<=m;i++)
{
if(sa[now[i]]&1)a[cnt][now[i]]=1;
if(sa[now[i]]&2)a[cnt][now[i]+n]=1;
}
cnt++,b[cnt]=(id>>1)&1;
for(int i=1;i<=m;i++)
{
if(sb[now[i]]&1)a[cnt][now[i]]=1;
if(sb[now[i]]&2)a[cnt][now[i]+n]=1;
}
}
bool gauss()
{
long long now=1,res=0;
for(int i=1;i<=n*2;i++)
{
long long mx=now;
for(int j=now;j<=cnt;j++)
if(a[j][i]!=0)
{
mx=j,res=max(res,mx);
break;
}
if(a[mx][i]==0)continue;
if(now!=mx)
{
for(int j=1;j<=n*2;j++)swap(a[now][j],a[mx][j]);
swap(b[now],b[mx]);
}
for(int j=now+1;j<=cnt;j++)
if(a[j][i]==1)
{
b[j]^=b[now];
for(int k=i;k<=n*2;k++)a[j][k]^=a[now][k];
}
y[now]=i;
now++;
}
for(int i=now;i<=cnt;i++)
if(b[i])return 0;
return 1;
}
void print(long long p)
{
if(ans[p]==0&&ans[p+n]==0)printf(".");
if(ans[p]==1&&ans[p+n]==0)printf("R");
if(ans[p]==0&&ans[p+n]==1)printf("Y");
if(ans[p]==1&&ans[p+n]==1)printf("B");
}
int main()
{
cin>>n>>k;
for(int i=1;i<=n;i++)sa[i]=1,sb[i]=2;
for(int i=1;i<=k;i++)
{
cin>>op;
if(op=="RY")ry();
else if(op=="RB")rb();
else if(op=="YB")yb();
else if(op=="mix")mix();
}
if(!gauss())printf("NO\n");
else
{
printf("YES\n");
ans[y[n*2]]=b[n*2];
for(int i=n*2-1;i>=1;i--)
{
ans[y[i]]=b[i];
for(int j=n*2;j>y[i];j--)
if(a[i][j])ans[y[i]]^=ans[j];
}
for(int i=1;i<=n;i++)print(i);
printf("\n");
}
return 0;
}
后记
矩阵这个知识点,推导简洁,码量较小,是一个比较简单的知识点。当然,也有可能是我水平有限,没有学习矩阵较难的内容。
不过,确实只有推过莫比乌斯反演和写过高级数据结构,才会发现一个推导简洁,码量较小的知识点确实很难得。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· 单线程的Redis速度为什么快?
· 展开说说关于C#中ORM框架的用法!
· Pantheons:用 TypeScript 打造主流大模型对话的一站式集成库
· SQL Server 2025 AI相关能力初探