十个利用矩阵乘法解决的经典题目

先贴模板:

矩阵乘法模板

struct Matrix{
	int row, col;
	LL m[MAXN][MAXN];
	void init(int row, int col){
		this->row = row;this->col = col;
		for (int i = 0; i < row; ++i)
			for (int j = 0; j < col; ++j)
				m[i][j] = 0;
	}
}C,I;  //输入和单位矩阵,单位矩阵初始化:I.m[i][j] = (i == j) ? 1 : 0;
Matrix operator*(const Matrix & a, const Matrix& b){
	Matrix res;
	res.init(a.row, b.col);
	for (int k = 0; k < a.col; ++k){
		for (int i = 0; i < res.row; ++i){
			if (a.m[i][k] == 0) continue;
			for (int j = 0; j < res.col; ++j){
				if (b.m[k][j] == 0) continue;
				res.m[i][j] = (a.m[i][k] * b.m[k][j] + res.m[i][j]) % mod;
			}
		}
	}
	return res;
}
Matrix operator+(const Matrix & a, const Matrix& b){
	Matrix res;
	res.init(a.row, b.col);
	for (int i = 0; i< a.col; ++i){
		for (int j = 0; j < res.row; ++j)
		{	res.m[i][j] = (a.m[i][j] + b.m[i][j] ) % mod;}
	}
	return res;
}
Matrix pow(Matrix A, LL n){
	Matrix ans = I, p = A;
	while (n){
		if (n & 1){	ans = ans*p;	n--;}
		n >>= 1;	p = p*p;
	}
	return ans;
}

经典题目1 :给定n个点,m个操作,构造O(m+n)的算法输出m个操作后各点的位置。操作有平移、缩放、翻 转和旋转

这里的操作是对所有点同时进行的。其中翻转是以坐标轴为对称轴进行翻转(两种情况),旋转则以原点为中心。如果对每个点分别进行模拟,那么m个操作总共耗时O(mn)。利用矩阵乘法可以在O(m)的时间里把所有操作合并为一个矩阵,然后每个点与该矩阵相乘即可直接得出最终该点的位置,总共耗时O(m+n)。假设初始时某个点的坐标为xy,下面5个矩阵可以分别对其进行平移、旋转、翻转和旋转操作。预先把所有m个操作所对应的矩阵全部乘起来,再乘以(x,y,1),即可一步得出最终点的位置。


经典题目 2 给定矩阵A,请快速计算出A^nnA相乘)的结果,输出的每个数都mod p

《快速幂模板》


经典题目 3 给定矩阵A,求A + A^2 + A^3 + + A^k的结果(两个矩阵相加就是对应位置 分别相加。输出数据mod m,k<=10^9 

Matrix sum(Matrix A, int k) //二分求(A+A^1+A^2+...A^k)%mod
{
	if (k == 1) return A;
	Matrix t = sum(A, k / 2);
	if (k & 1)
	{
		Matrix cur = pow(A, k / 2 + 1); t = t + (t*cur); t = t + cur;
	}
	else
	{
		Matrix cur = pow(A, k / 2); t = t + (t*cur);
	}
	return t;
}


经典题目 4 题目大意:顺次给出m个置换,反复使用这m个置换对初始序列进行操作,问k次置换后的序列。M<=10,k<2^31

例题:VOJ 1049 注意理清楚到达谁乘谁,不同的初始化对应不同的乘法次序

Matrix temp[15], ans; //操作矩阵,结果
int main(){
	int N, M, K, num;
	while (scanf("%d%d%d", &N, &M, &K) != EOF){
		I.init(N, N); C.init(N, 1); //初始化单位矩阵I和初始矩阵C。
		for (int i = 0; i < N; i++){
			I.m[i][i] = 1; C.m[i][0] = i+1;
		}
		ans = I;
		for (int i = 0; i < M; i++) //对于每一行操作。转换成一个n*n的操作矩阵。
		{
			temp[i].init(N, N);
			for (int j = 0; j < N; j++){
				scanf("%d", &num); temp[i].m[j][num-1] = 1;
			}
		}
		for (int i = 0; i < M; i++) //M个操作相乘为一个循环后的操作矩阵
		{
			ans = temp[i]*ans;
		}
		int div = K / M; //需要循环多少次
		int rem = K%M;  //剩下需要模拟多少次
		ans = pow(ans, div); //矩阵快速幂
		for (int i = 0; i < rem; i++) //模拟最后的求余次
		{ ans = temp[i] * ans; }
		ans = ans*C; //最终结果矩阵*初始矩阵=结果
		for (int i = 0; i < N; i++){
			printf("%d", ans.m[i][0]);
			if (i != N - 1) { printf(" "); }
			Else { printf("\n"); }
		}
	}
}


经典题目5 《算法艺术与信息学竞赛》207页(2.1代数方法和模型,[例题5]细菌,版次不同可能页码有偏差)


经典问题6 给定np,求第nFibonaccimod p的值,n不超过 2^31

<<挑战程序设计竞赛 P199>>


经典问题7   例题:VOJ 1067

求出任何一个线性递推式的第n项,其对应矩阵的构造方法为: 在右上角的(n-1)*(n-1)的小矩阵中的主对角线上填 1,矩阵第n行填对应的系数,其它地方都填 0例如,我们可以用下面的矩阵乘法来二分计算f(n) = 4f(n-1) - 3f(n-2) + 2f(n-4)的第k项: 


Matrix temp;
int main(){
	LL n;  int k;
	while (cin>>k>>n){
		C.init(k, k);  I.init(k, k);
		for (int i = 0; i < k; i++){
			C.m[i][0] = 1;
			for (int j = 0; j < i; j++){
				C.m[i][0] += C.m[j][0];
			}
			I.m[i][i] = 1;
		}
		temp.init(k, k);
		for (int i = 0; i < k; i++){
			if (i < k-1){ //右上角矩阵
				temp.m[i][i+1] = 1;
			}
			temp.m[k - 1][i] = 1; //最后一行,此题系数为1
		}
		temp = pow(temp, n - 1);
		C = temp*C;
		cout << C.m[0][0]%mod << endl;
	}
}

经典问题 8 给定一个有向图,问从A点恰好走k步(允许重复经过边)到达B点的方案数mod   <<挑战程序竞赛 P203>>


经典题目 9 1 x 2 的多米诺骨牌填满M x N的矩形有多少种方案,M<=5N<2^31,输出 答案mod p的结果


#define MAXN 258
#define MOD 9937
#define size (1<<M)
int N, M;
int ans[1200];
class Matrix{
  public:
    int mt[MAXN][MAXN];
    Matrix  Multiply(Matrix);
    Matrix  Add(Matrix);
    Matrix  quickpower(int);
}AA;
Matrix Matrix::Add(Matrix A){
   Matrix C;
   for( int i = 1; i <= size; i++)
     for( int j = 1; j <= size; j++)
         C.mt[i][j] = mt[i][j] + A.mt[i][j];                     
   return C;
}
Matrix Matrix::Multiply(Matrix A){
   Matrix C;
   for( int i = 0; i < size; i++) {
       for( int j = 0; j < size; j++){
            
            int sum = 0;
            for( int k = 0; k < size; k++) {
              if( A.mt[i][k] && mt[k][j] ){    
               sum += (A.mt[i][k] % MOD) * (mt[k][j] % MOD);
               sum %= MOD;
               } 
            }     
            C.mt[i][j] = sum;
       }         
   }
   return C;     
}
Matrix Matrix::quickpower(int n){
  Matrix B;
  B = *this;
  while( n > 0 ) {
     if( n & 1 )
         *this = (*this).Multiply(B);
     n = n / 2;
     B = B.Multiply(B);             
  }
  return *this;
}
void pre(int x, int state){//横的摆放状态 
  if( x > M )
      return;
  if( x == M ) {
    ans[state] = 1;
    return;
  }
  pre(x + 1, state << 1 );
  pre(x + 2, 3 | (state << 2) ); 
}
int main( ){
  while( scanf("%d%d",&N,&M) != EOF ){
    if( N < M )
        swap(N,M);
    memset(ans, 0, sizeof(ans));
    pre(0, 0);
    if( N % 2 == 1 && M % 2 == 1 ){
       puts("0"); continue;
    }
    for( int i = 0; i < (1<<M); i++){
        for( int j = 0; j < (1<<M); j++){   
            AA.mt[i][j] = 0;
            if( ((~i)&j) == ((~i)&((1<<M)-1)) )
            { AA.mt[i][j] = ans[i&j];      }     
        }     
    }
    AA = AA.quickpower(N-1);
    printf("%d\n",AA.mt[(1<<M)-1][(1<<M)-1]);       
  }  
}

经典题目 10 题目大意是,检测所有可 的DNA只能由ACTG四个字符构成。题目将给出 10 个以内的病毒片段, 能的nDNA串有多少个DNA串中不含有指定的病毒片段。合法 每个片段长度不超10, 数据规模n<=2000000000

struct Matrix{
	unsigned long long mat[40][40];
	int n;
	Matrix(){}
	Matrix(int _n){
		n = _n;
		for (int i = 0; i<n; i++)
			for (int j = 0; j<n; j++)
				mat[i][j] = 0;
	}
	Matrix operator *(const Matrix &b)const{
		Matrix ret = Matrix(n);
		for (int i = 0; i<n; i++)
			for (int j = 0; j<n; j++)
				for (int k = 0; k<n; k++)
					ret.mat[i][j] += mat[i][k] * b.mat[k][j];
		return ret;
	}
};
unsigned long long pow_m(unsigned long long a, int n){
	unsigned long long ret = 1;
	unsigned long long tmp = a;
	while (n)
	{
		if (n & 1)ret *= tmp;
		tmp *= tmp;
		n >>= 1;
	}
	return ret;
}
Matrix pow_M(Matrix a, int n){
	Matrix ret = Matrix(a.n);
	for (int i = 0; i<a.n; i++)
		ret.mat[i][i] = 1;
	Matrix tmp = a;
	while (n){
		if (n & 1)ret = ret*tmp;
		tmp = tmp*tmp;
		n >>= 1;
	}
	return ret;
}
struct Trie{
	int next[40][26], fail[40];
	bool end[40];
	int root, L;
	int newnode(){
		for (int i = 0; i < 26; i++)
			next[L][i] = -1;
		end[L++] = false;
		return L - 1;
	}
	void init(){L = 0; root = newnode();}
	void insert(char buf[]){
		int len = strlen(buf);
		int now = root;
		for (int i = 0; i < len; i++){
			if (next[now][buf[i] - 'a'] == -1)
				next[now][buf[i] - 'a'] = newnode();
			now = next[now][buf[i] - 'a'];
		}
		end[now] = true;
	}
	void build(){
		queue<int>Q;
		fail[root] = root;
		for (int i = 0; i < 26; i++)
			if (next[root][i] == -1)
				next[root][i] = root;
			else
			{
				fail[next[root][i]] = root;
				Q.push(next[root][i]);
			}
		while (!Q.empty())
		{
			int now = Q.front();
			Q.pop();
			if (end[fail[now]])end[now] = true;
			for (int i = 0; i < 26; i++)
				if (next[now][i] == -1)
					next[now][i] = next[fail[now]][i];
				else
				{
					fail[next[now][i]] = next[fail[now]][i];
					Q.push(next[now][i]);
				}
		}
	}
	Matrix getMatrix()
	{
		Matrix ret = Matrix(L + 1);
		for (int i = 0; i < L; i++)
			for (int j = 0; j < 26; j++)
				if (end[next[i][j]] == false)
					ret.mat[i][next[i][j]] ++;
		for (int i = 0; i < L + 1; i++)
			ret.mat[i][L] = 1;
		return ret;
	}
	void debug()
	{
		for (int i = 0; i < L; i++)
		{
			printf("id = %3d,fail = %3d,end = %3d,chi = [", i, fail[i], end[i]);
			for (int j = 0; j < 26; j++)
				printf("%2d", next[i][j]);
			printf("]\n");
		}
	}
};
char buf[10];
Trie ac;
int main(){
	int n, L;
	while (scanf("%d%d", &n, &L) == 2){
		ac.init();
		for (int i = 0; i < n; i++)
		{ scanf("%s", buf); ac.insert(buf); }
		ac.build();
		Matrix a = ac.getMatrix(); a = pow_M(a, L);
		unsigned long long res = 0;
		for (int i = 0; i < a.n; i++)
			res += a.mat[0][i];
		res--;
		/*
		* f[n]=1 + 26^1 + 26^2 +...26^n
		* f[n]=26*f[n-1]+1
		* {f[n] 1} = {f[n-1] 1}[26 0;1 1]
		* 数是f[L]-1;
		* 此题的L<2^31.矩阵的幂不能是L+1次,否则就超时了
		*/
		a = Matrix(2);
		a.mat[0][0] = 26; a.mat[1][0] = a.mat[1][1] = 1; a = pow_M(a, L);
		unsigned long long ans = a.mat[1][0] + a.mat[0][0];
		ans--; ans -= res;
		cout << ans << endl;
	}
}



  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值