Loading

【笔记】矩阵/线性基

来自\(\texttt{SharpnessV}\)省选复习计划中的矩阵/线性基


P3390 【模板】矩阵快速幂

模板。

矩阵乘法是\(N^3\)的,快速幂是\(\log T\),总的时间复杂度为\(\rm O(N^3\log T)\)

#include<bits/stdc++.h>
#define rep(i,a,b) for(int i=a;i<=b;i++)
#define pre(i,a,b) for(int i=a;i>=b;i--)
#define N 105
#define P 1000000007
using namespace std;
int n;long long k;
struct node{
	int a[N][N];
	node(){memset(a,0,sizeof(a));}
	void init(){rep(i,1,n)a[i][i]=1;}
};
node operator*(node x,node y){
	node now;
	rep(i,1,n)rep(j,1,n)rep(w,1,n)
		now.a[i][j]=(now.a[i][j]+1LL*x.a[i][w]*y.a[w][j])%P;
	return now;
}
node operator^(node x,long long y){
	node now;now.init();
	for(;y;y>>=1,x=x*x)if(y&1)now=now*x;
	return now;
}
int main(){
	scanf("%d%lld",&n,&k);
	node cur;
	rep(i,1,n)rep(j,1,n)scanf("%d",&cur.a[i][j]);
	cur=cur^k;
	rep(i,1,n){rep(j,1,n)printf("%d ",cur.a[i][j]);putchar('\n');}
	return 0;
}

P1939 【模板】矩阵加速(数列)

定义初始矩阵。

\[A=\begin{bmatrix}f_1&f_2&f_3\end{bmatrix} \]

定义转移矩阵。

\[B=\begin{bmatrix}0&0&1\\1&0&0\\1&0&1\end{bmatrix} \]

\[A\times B^{n-1}=\begin{bmatrix}f_n&f_{n+1}&f_{n+2}\end{bmatrix} \]

最后矩阵快速幂即可。

#include<bits/stdc++.h>
#define rep(i,a,b) for(int i=a;i<=b;i++)
#define pre(i,a,b) for(int i=a;i>=b;i--)
#define P 1000000007LL
#define N 3
typedef long long ll;
using namespace std;
struct mat{
	int a[N][N];
	mat(){memset(a,0,sizeof(a));}
	void init(){
		rep(i,0,2)a[i][i]=1;
	}
	void build(){
		a[0][0]=a[0][1]=a[1][2]=a[2][0]=1;
	}
};
mat operator*(const mat x,const mat y){
	mat now;
	rep(i,0,2)rep(j,0,2)rep(k,0,2)
		now.a[i][j]=1LL*(now.a[i][j]+1LL*x.a[i][k]*y.a[k][j])%P;
	return now;
}
mat operator^(mat x,int y){
	mat now;now.init();
	for(;y;y>>=1,x=x*x)if(y&1)now=now*x;
	return now;
}
int main(){
	int T;scanf("%d",&T);
	while(T--){
		int n;scanf("%d",&n);
		if(n<=3){puts("1");continue;}
		mat now;now.build();now=now^(n-3);
		printf("%lld\n",((now.a[0][0]+now.a[1][0])%P+now.a[2][0])%P);
	}
	return 0;
}

P1962 斐波那契数列

P1349 广义斐波那契数列

P5343 【XR-1】分块

矩阵递推模板,留白。

P5337 [TJOI2019]甲苯先生的字符串

矩阵还可以用来解决一类图上模型。

我们给定邻接矩阵\(A\),令\(B=A^n\),则\(B_{i,j}\)表示节点 \(i\) 经过 \(n\) 步到达 \(i\) 的方案数。

这道题相邻两个字母可以看成字母 \(i\) 到字母 \(j\) 的转移,经过 \(\rm Len-1\) 步转移。直接套用模板即可。

#include<bits/stdc++.h>
#define rep(i,a,b) for(int i=a;i<=b;i++)
#define pre(i,a,b) for(int i=a;i>=b;i--)
#define N 27
#define P 1000000007
using namespace std;
long long n;
struct node{
	int a[N][N];
	node(){memset(a,0,sizeof(a));}
	void init(){
		rep(i,0,25)a[i][i]=1;
	}
	node operator*(node o){
		node now;
		rep(i,0,25)rep(j,0,25)rep(k,0,25)
			now.a[i][j]=(now.a[i][j]+1LL*a[i][k]*o.a[k][j])%P;
		return now;
	}
	node operator^(long long y){
		node x=*this,now;now.init();
		for(;y;y>>=1,x=x*x)if(y&1)now=now*x;
		return now;
	}
}w;
char s[1<<20];
int main(){
	scanf("%lld",&n);
	rep(i,0,25)rep(j,0,25)w.a[i][j]=1;
	scanf("%s",s+1);int m=strlen(s+1);
	rep(i,1,m-1)w.a[s[i]-'a'][s[i+1]-'a']=0;
	w=w^(n-1);int ans=0;
	rep(i,0,25)rep(j,0,25)ans=(ans+w.a[i][j])%P;
	printf("%d\n",ans);
	return 0;
}

P6772 [NOI2020] 美食家

广义矩阵乘法,希望不会有人再因为基础科技不会而签到题丢分。

这里我们定义矩阵乘法 \(C_{i,j}=\max\limits_{k}\{A_{i,k}+ B_{k,j}\}\) 。不难证明这仍然满足结合律。

\[dp_{k}=dp_{k-1}\times G \]

我们仍然使用矩阵快速幂优化,一个小技巧是预处理矩阵的\(2^k\)次幂。

代码


【模板】高斯消元法

基础科技。先消成上三角矩阵,然后回代即可,时间复杂度\(\rm O(N^3)\)

#include<bits/stdc++.h>
#define rep(i,a,b) for(int i=a;i<=b;i++)
#define pre(i,a,b) for(int i=a;i>=b;i--)
#define N 105
using namespace std;
int n;double a[N][N];
void solve(){
	rep(i,1,n){
		double cur=a[i][i];
		if(!a[i][i]){puts("No Solution");return;}
		rep(j,i,n+1)a[i][j]/=cur;
		rep(j,i+1,n)pre(k,n+1,i)a[j][k]-=a[j][i]*a[i][k];
	}
	pre(i,n,1)rep(j,1,i-1)a[j][n+1]-=a[j][i]*a[i][n+1];
	rep(i,1,n)printf("%.2lf\n",a[i][n+1]);
}
int main(){
	scanf("%d",&n);
	rep(i,1,n)rep(j,1,n+1)scanf("%lf",&a[i][j]);
	solve();
	return 0;
} 

【模板】矩阵求逆

一个没多大实际作用的算法,挂这里。


P4035 [JSOI2008]球形空间产生器

高斯消元。我们将球心每一维的坐标当作未知数,可以得到 \(n+1\)\(n+1\) 元的二次方程。

方程之间差分,可以得到 \(n\) 元线性方程,高斯消元即可。

代码


【模板】线性基

线性基用于解决一类异或问题。

\(N\)个数中选出若干个数,使得他们的异或和最大。如果是取出两个或三个数,我们还可以通过使用 \(\rm Trie\) 树获得最优的复杂度,但任意取显然是做不到的。

线性基基于这样一个事实,就是 \(N\) 个数任意选的方案数是\(2^N\),如果最高位数是\(k\),则异或和的取值方案不超过 \(2^k\)。如果 \(N\) 大于 \(k\),我们可以保留 \(k\) 个最具代表性的数代替 \(N\) 个数。

这就是线性基的核心,用若干个最高位不同的关键数替换原来的\(N\)个数,使得在求异或和的情况下等价。

线性基是动态的,可以支持插入操作,每次我们拆入一个数,找到第一个没有取值的位 \(i\) ,将第 \(i\) 的取值置为当前数。

#include<bits/stdc++.h>
#define rep(i,a,b) for(int i=a;i<=b;i++)
#define pre(i,a,b) for(int i=a;i>=b;i--)
#define N 66
using namespace std;
int n;long long x,a[N];
int main(){
	scanf("%d",&n);
	rep(i,1,n){
		scanf("%lld",&x);
		pre(i,50,0)if((x>>i)&1){
			if(!a[i]){a[i]=x;break;} 
			x^=a[i];
		}
	}
	long long ans=0;
	pre(i,50,0)if(!((ans>>i)&1))ans^=a[i];
	printf("%lld\n",ans);
	return 0;
}

P3857 [TJOI2008]彩灯

因为线性基中的 \(k\) 个数等价于原 \(N\) 个数,且线性基中的数异或和互不相同,所以答案就是 \(2^k\)

代码

P4570 [BJWC2011]元素

贪心。

如果存在若干个数 \(\rm a_1\ xor\ a_2\ \cdots xor\ a_n=0\),则一定要去除且仅其中的一个数,显然去除的数权值越小越好。

所以我们将所有数按照从大到小排序,如果能加入线性线性基就选择当前数,否则就把它丢弃。

代码

P4301 [CQOI2013] 新Nim游戏

第一回合只用将石子取到剩下的石子无论再取多少堆异或和都\(\neq 0\)就赢了。

转换一下就是留下最多的石子使得子集异或和不为 \(0\)。所以本质和上面是同一题。

P3292 [SCOI2016]幸运数字

线性基支持动态加入,当然可以支持树上倍增。

代码


P4151 [WC2011]最大XOR和路径

我们可以任取一条\(1\to N\)的路径,然后发现如果要改变路径,则需要异或一个环的异或和。

我们把所有环的权值插入线性基,然后查询异或最大值即可。

#include<bits/stdc++.h>
#define rep(i,a,b) for(int i=a;i<=b;i++)
#define pre(i,a,b) for(int i=a;i>=b;i--)
#define N 50005
using namespace std;
typedef long long ll;
int h[N],tot;
struct edge{
	int to,nxt;ll val;
}e[N<<2];
void add(int x,int y,ll z){
	e[++tot].nxt=h[x];h[x]=tot;e[tot].to=y;e[tot].val=z;
} 
int n,m;bool v[N];
queue<int>q;ll p[63],d[N];
void ins(ll x){
	pre(i,60,0){
		if(!x)return;
		if(!((x>>i)&1))continue;
		if(!p[i]){p[i]=x;return;}
		x^=p[i];
	}
}
void dfs(int x,ll val){
	d[x]=val;v[x]=1;
	for(int i=h[x];i;i=e[i].nxt)
		if(!v[e[i].to])dfs(e[i].to,val^e[i].val);
		else ins(d[e[i].to]^d[x]^e[i].val);
}
ll ask(ll x){
	ll ans=x;
	pre(i,60,0)if(!((ans>>i)&1))ans^=p[i];
	return ans;
}
int main(){
	scanf("%d%d",&n,&m);
	rep(i,1,m){
		int x,y;ll z;
		scanf("%d%d%lld",&x,&y,&z);
		add(x,y,z);add(y,x,z);
	}
	dfs(1,0);printf("%lld\n",ask(d[n]));
	return 0;
}
posted @ 2021-12-16 17:29  7KByte  阅读(98)  评论(0编辑  收藏  举报