矩阵是干什么的呢?一句话来说就是,知道相邻两个函数的递推关系和第一个数,让你递推到第n个数。显然,如果n很大,那么一个一个递推过去是会超时的。所以矩阵就是用来解决这种快速递推的问题的。

  比方说斐波那契数列就是一个递推的典型。

  先丢题目链接:我是题目!

  那么问题的关键就变成了如何找递推关系的中介矩阵temp了。如果题目告诉了你递推关系,题目就变得很简单了。但是告诉你递推关系大致可分为两类,一类是加法的递推,像斐波那契数列,temp矩阵的每个元素都是常数。另外一种,就是有乘法的递推关系了,这类关系一般需要凑出来,有时候隐晦的话也不是那么容易的。比如说上面题目中的I题,就是需要拼凑的。

  总之先交代模板,先看H题。这题求矩阵sum(A)=A^1+A^2+A^3+...+A^n。方法的话是二分,左边这个式子如果n是偶数的话可以拆成(1+A^(n/2))*(A^1+A^2+...+A(n/2)),然后右边部分又可以递归,不断二分地递归即可。当然,如果n是奇数要再加上A^n;另外上面式子中的1在这里指的是同阶的单位矩阵。代码如下(也可以直接当做矩阵的模板了):

 #include <stdio.h>
#include <algorithm>
#include <math.h>
#include <vector>
#include <map>
#include <set>
#include <iostream>
#include <string.h>
#pragma comment(linker, "/STACK:1024000000,1024000000")
using namespace std;
typedef long long ll;
typedef pair<int,int> pii;
const int mod = ; //int mod; void add(int &a,int b)
{
a += b;
if(a < ) a += mod;
//if(a >= mod) a -= mod;
a %= mod;
} struct matrix
{
int e[+][+],n,m;
matrix() {}
matrix(int _n,int _m): n(_n),m(_m) {memset(e,,sizeof(e));}
matrix operator * (const matrix &temp)const
{
matrix ret = matrix(n,temp.m);
for(int i=;i<=ret.n;i++)
{
for(int j=;j<=ret.m;j++)
{
for(int k=;k<=m;k++)
{
add(ret.e[i][j],1LL*e[i][k]*temp.e[k][j]%mod);
}
}
}
return ret;
}
matrix operator + (const matrix &temp)const
{
matrix ret = matrix(n,m);
for(int i=;i<=n;i++)
{
for(int j=;j<=m;j++)
{
add(ret.e[i][j],(e[i][j]+temp.e[i][j])%mod);
}
}
return ret;
}
void getE()
{
for(int i=;i<=n;i++)
{
for(int j=;j<=m;j++)
{
e[i][j] = i==j?:;
}
}
}
}; matrix qpow(matrix temp,int x)
{
int sz = temp.n;
matrix base = matrix(sz,sz);
base.getE();
while(x)
{
if(x & ) base = base * temp;
x >>= ;
temp = temp * temp;
}
return base;
} void print(matrix p)
{
int n = p.n;
int m = p.m;
for(int i=;i<=n;i++)
{
for(int j=;j<=m;j++)
{
printf("%d ",p.e[i][j]);
}
cout << endl;
}
} matrix solve(matrix a, int k)
{
if(k == ) return a;
int n = a.n;
matrix temp = matrix(n,n);
temp.getE();
if(k & )
{
matrix ex = qpow(a,k);
k--;
temp = temp + qpow(a,k/);
return temp * solve(a,k/) + ex;
}
else
{
temp = temp + qpow(a,k/);
return temp * solve(a,k/);
}
} int main()
{
int n,k;
while(scanf("%d%d",&n,&k)== && n)
{
matrix a = matrix(n,n);
for(int i=;i<=n;i++)
{
for(int j=;j<=n;j++) {scanf("%d",&a.e[i][j]);a.e[i][j] %= mod;}
// 这里矩阵的每个元素在输入以后就要mod一下,不然可能会因为输入的元素太大导致在后面爆int(?)
}
matrix ans = solve(a,k);
for(int i=;i<=n;i++)
{
for(int j=;j<=n;j++)
{
printf("%d%c",ans.e[i][j],j==n?'\n':' ');
}
}
puts("");
}
}

矩阵模板

  还要讲的几点是:1.E题中的类型,A是1000*6的矩阵,B是6*1000的矩阵,现在求(A*B)^N,显然1000*1000的矩阵是要超时的,那么不妨化成A*(B*A)^(n-1)*B,这样B*A是6*6的矩阵,就快多了;2.矩阵不要无脑开的很大,因为我发现有时候矩阵开大了时间会增加很多;3.关于循环矩阵的问题,比方说一个矩阵,它的每一行都是和第一行一样的,只是位置每行错开一个位置(当然列也是一样),这样的矩阵就叫做循环矩阵,在计算的过程中只要保存一行即可(即使是一行也可以知道整个矩阵的内容了),这样的题目出现在J题。这题的代码如下:

 #include <stdio.h>
#include <algorithm>
#include <math.h>
#include <vector>
#include <map>
#include <set>
#include <iostream>
#include <string.h>
#pragma comment(linker, "/STACK:1024000000,1024000000")
using namespace std;
typedef pair<int,int> pii;
//const int mod = 1000; int n,mod,d,k; void add(int &a,int b)
{
a += b;
if(a < ) a += mod;
//if(a >= mod) a -= mod;
a %= mod;
} struct matrix
{
int e[][+],n,m;
matrix() {}
matrix(int _n,int _m): n(_n),m(_m) {memset(e,,sizeof(e));}
matrix operator * (const matrix &temp)const
{
matrix ret = matrix(,m);
for(int i=;i<m;i++)
{
for(int j=;j<m;j++)
{
add(ret.e[][i],1LL*e[][j]*temp.e[][((j-i)%m+m)%m]%mod);
}
}
return ret;
} }; matrix qpow(matrix temp,int x)
{
int sz = temp.m;
matrix base = matrix(,sz);
base.e[][] = ;
while(x)
{
if(x & ) base = base * temp;
x >>= ;
temp = temp * temp;
}
return base;
} void print(matrix p)
{
int n = p.n;
int m = p.m;
for(int i=;i<n;i++)
{
for(int j=;j<m;j++)
{
printf("%d ",p.e[i][j]);
}
cout << endl;
}
} int main()
{
while(scanf("%d%d%d%d",&n,&mod,&d,&k)==)
{
matrix ans = matrix(,n);
for(int i=;i<n;i++) scanf("%d",&ans.e[][i]);
matrix temp = matrix(,n);
//for(int i=0;i<n;i++)
{
temp.e[][]=;
for(int j=;j<=d;j++)
{
int now = +j;
if(now >= n) now -= n;
temp.e[][now] = ;
}
for(int j=;j<=d;j++)
{
int now = -j;
if(now < ) now += n;
temp.e[][now] = ;
}
}
//print(temp);
temp = qpow(temp,k);
ans = ans * temp;
for(int i=;i<n;i++)
{
printf("%d%c",ans.e[][i],i==n-?'\n':' ');
}
}
}

循环矩阵

要注意之所以这份代码里面的qpow函数的单位矩阵可以直接相乘,是因为单位矩阵也是一个循环矩阵,且循环性质和题中所给的矩阵一样。

  另外,这份题目中其他题目的好题如下:B,C,M,N,P,R。

  以上几题想稍微补充一下的是,M题,ceil里面的数加上它的共轭函数刚好是一个整数,证明如下:展开以后,有根号b的部分,如果其次数是偶数,那么一定是整数,如果是奇数,那么共轭的两个这个部分刚好一正一负,相抵消。这点证明了以后,又因为(a-1) 2< b < a2,因此(a-根号b)在0到1之间,所以共轭的两部分相加刚好是前面部分取ceil的值,即所求的没有%m的部分。对R题,虽然题目不算难,但是要注意的是,A^B%C,不能直接取余,要结合数论中的知识来,如下:

 /*
A^B %C 这题的C是质数,而且A,C是互质的。
所以直接A^(B%(C-1)) %C 比较一般的结论是 A^B %C=A^( B%phi(C)+phi(C) ) %C , B>=phi(C)
*/

  最后想说的是,D题和L题并没有做,而且P题最后部分也没法很好的证明,P题还是留个代码吧:

 #include <stdio.h>
#include <algorithm>
#include <math.h>
#include <vector>
#include <map>
#include <set>
#include <iostream>
#include <string.h>
using namespace std;
typedef long long ll;
typedef pair<int,int> pii;
//const int mod = 1e9 + 7; int mod; void add(int &a,int b)
{
a += b;
if(a < ) a += mod;
//if(a >= mod) a -= mod;
a %= mod;
} struct matrix
{
int e[][];
int n,m;
matrix() {}
matrix(int _n,int _m): n(_n),m(_m) {memset(e,,sizeof(e));}
matrix operator * (const matrix &temp)const
{
matrix ret = matrix(n,temp.m);
for(int i=;i<=ret.n;i++)
{
for(int j=;j<=ret.m;j++)
{
for(int k=;k<=m;k++)
{
add(ret.e[i][j],1LL*e[i][k]*temp.e[k][j]%mod);
}
}
}
return ret;
}
matrix operator + (const matrix &temp)const
{
matrix ret = matrix(n,m);
for(int i=;i<=n;i++)
{
for(int j=;j<=m;j++)
{
add(ret.e[i][j],(e[i][j]+temp.e[i][j])%mod);
}
}
return ret;
}
void getE()
{
for(int i=;i<=n;i++)
{
for(int j=;j<=m;j++)
{
e[i][j] = i==j?:;
}
}
}
}; matrix qpow(matrix temp,int x)
{
int sz = temp.n;
matrix base = matrix(sz,sz);
base.getE();
while(x)
{
if(x & ) base = base * temp;
x >>= ;
temp = temp * temp;
}
return base;
} void print(matrix p)
{
int n = p.n;
int m = p.m;
for(int i=;i<=n;i++)
{
for(int j=;j<=m;j++)
{
printf("%d ",p.e[i][j]);
}
cout << endl;
}
} void Set(matrix &temp,int x,int y,int op)
{
temp.e[x][y] = op;
} matrix solve(matrix temp,int x)
{
if(x==) return temp;
matrix ret = matrix(,);
ret.getE();
if(x & )
{
return (ret+qpow(temp,x/)) * solve(temp,x/) + qpow(temp,x);
}
else
{
return (ret+qpow(temp,x/)) * solve(temp,x/);
}
} void getAns(int r) {
//printf("Yes\n");
int ans[][];
memset(ans, -, sizeof(ans));
for (int i = r / + ; i <= r; i++)
ans[i][i - (r / )] = ;
for (int i = r / + ; i <= r; i++)
for (int j = ; j <= (i - r / - ); j++)
ans[i][j] = ;
for (int i = r / + ; i <= r; i++)
for (int j = r - i + ; j <= r; j++)
ans[j][i] = ;
for (int i = ; i <= r; i++) {
for (int j = ; j <= r; j++) {
printf("%d", ans[i][j]);
printf(j == r ? "\n" : " ");
}
}
} int main()
{
/*
http://www.cnblogs.com/Torrance/p/5412802.html
*/ int n;
int T;cin >>T;
for(int kase=;kase<=T;kase++)
{
scanf("%d%d",&n,&mod);
printf("Case %d: ",kase);
/*matrix ans = matrix(1,2);
ans.e[1][1] = ans.e[1][2] = 1; matrix temp = matrix(2,2);
temp.e[1][2] = temp.e[2][1] = temp.e[2][2] = 1;
temp = solve(temp,n-1); matrix t = matrix(2,2);t.getE();
temp = temp + t; ans = ans * temp;
int sum = ans.e[1][1];*/ // 上面的方法是这样的:
/*
(F1,F2)* temp(构造矩阵) = (F2,F3)
同理的:(F1,F2)* temp^2 = (F3,F4)
...
那么只要(F1,F2)*(temp^0+temp^1+...+temp^(n-1)),这个矩阵的第一个元素即是sum
这是矩阵的分配律。而后面部分只要二分即可求得
*/ // 下面是常规的方法(?)
/*
(F1,F2,sum(初始化为F1))*temp^(n-1)
sum可以理解为加到F1(第一个元素)为止的元素的和,第二个元素可以理解为当前要加的元素
temp为:
0 1 0
1 1 1
0 0 1
*/ matrix ans = matrix(,);
for(int i=;i<=;i++) Set(ans,,i,);
matrix temp = matrix(,);
Set(temp,,,);Set(temp,,,);Set(temp,,,);Set(temp,,,);Set(temp,,,); temp = qpow(temp,n-);
ans = ans * temp;
int sum = ans.e[][]; //cout << sum <<endl; cout << (sum% || sum == ? "No":"Yes") <<endl;
if(sum % == && sum != ) getAns(sum);
}
}

D题有个很好的博客在这儿:D题。关于Q题我找出的递推关系如下:

g(x)=g(x-1)+g(x-2)+1,(f(x)要调用一次,所以要加1)。

似乎还有一种更好的方法但是我不是很理解。。

  那么,矩阵就暂时告一段落了~

————————————————隔日的分界线——————————————————————

  这里写于第二天,因为发现昨天总结的过于匆忙,有个东西忘记讲了。关于E题,这种既需要1000*6的矩阵又需要6*1000的矩阵的,如果是结构体,那么就需要开1000*1000从而照顾他们都能实现,但是1000*1000的话结构体是开不下的(程序会爆栈而直接崩溃)。所以这里需要一种新的写法,vector的写法,这样的话,想开多少开多少即可。但是我不是很喜欢这种写法,所以这里只是顺带提供一下这样写法的模板,以备不时之需。E题代码如下:

 #include <stdio.h>
#include <algorithm>
#include <math.h>
#include <vector>
#include <map>
#include <set>
#include <iostream>
#include <string.h>
using namespace std;
typedef pair<int,int> pii;
const int mod = ; int n,k; typedef vector<int> vec ;
typedef vector<vec> mat ; inline void add (int &a ,int b) {
a += b ; if(a<) a+=mod; a%=mod ;
} mat mul (const mat &a , const mat &b) {
mat ret(a.size(),vec(b[].size())) ;
for (int i= ; i<a.size() ; i++)
for (int j= ; j<b[].size() ; j++)
for (int z= ; z<b.size() ; z++)
add(ret[i][j],a[i][z]*b[z][j]%mod) ;
return ret ;
} int main () {
while(scanf("%d%d",&n,&k)==)
{
if(n== && k==) break;
mat a(n,vec(k));
mat b(k,vec(n));
for(int i=;i<n;i++)
{
for(int j=;j<k;j++) scanf("%d",&a[i][j]);
}
for(int i=;i<k;i++)
{
for(int j=;j<n;j++) scanf("%d",&b[i][j]);
}
mat c =mul(b,a);
mat base(c.size(),vec(c.size()));
for(int i=;i<c.size();i++) base[i][i] = ;
/*
如果直接算A*B的话,得到的是1000*1000的矩阵,所以会一直超时。
因为C^(n*n)=(A*B)^(n*n)=A*(B*A)^(n*n-1)*B,由于B*A是6*6的矩阵,再用矩阵快速幂来求就行了。
*/
int temp = n*n-;
while(temp)
{
if(temp & ) base = mul(base,c);
temp >>= ;
c = mul(c,c);
}
mat ans = mul(mul(a,base),b);
int sum = ;
for(int i=;i<n;i++)
{
for(int j=;j<n;j++) sum += ans[i][j];
}
cout << sum << endl;
}
return ;
}

vector的矩阵写法

————————————————2017/9/4的分界线————————————————————

  考虑到矩阵稀疏的情况,有些题目(如poj3735)需要进行稀疏矩阵的乘法优化:若矩阵A*B,枚举A的每个位置A.e[i][j],若其不为0,则能做出贡献,在乘法A.e[i][j]*B.e[j][k]做出贡献,那么接着枚举k即可。

  另外上面的模板需要注意的一个地方是,由于每次乘法都会创造一个ret矩阵且伴随着一次memset,因此矩阵大小建议按照实际的大小设置(由于1base所以大小=size+1)否则可能出现TLE的情况。

ACM之路(18)—— 矩阵的更多相关文章

  1. nyist 606 ACM之路

    http://acm.nyist.net/JudgeOnline/problem.php?pid=606 ACM之路 时间限制:1000 ms | 内存限制:65535 KB 描述 转眼间,12级新生 ...

  2. “玲珑杯”ACM比赛 Round #18

    “玲珑杯”ACM比赛 Round #18 Start Time:2017-07-15 12:00:00 End Time:2017-07-15 15:46:00 A -- 计算几何你瞎暴力 Time ...

  3. 另一个ACM之路建议

    ACM联系建议 一位高手对我的建议: 一般要做到50行以内的程序不用调试.100行以内的二分钟内调试成功.acm主要是考算法的 ,主要时间是花在思考算法上,不是花在写程序与debug上. 下面给个计划 ...

  4. 剪辑的楼天城的ACM之路

    楼天城楼教主的acm心路历程(剪辑) 利用假期空闲之时,将这几年GCJ,ACM,TopCoder 参加的一些重要比赛作个回顾.昨天是GCJ2006 的回忆,今天时间上更早一些吧,我现在还清晰记得3 年 ...

  5. ACM之路(转载)

    转载自:https://www.cnblogs.com/tianjintou/p/4139416.html 要注意,ACM的竞赛性强,因此自己应该和自己的实际应用联系起来. 适合自己的才是好的,有的人 ...

  6. ACM之路——上车了

    校赛坚持到底,拿到了银牌:第一批进入ACM队集训,期末考试之前仍然代码不断,甚至感觉对不起大学第一次的期末考试,五天复习高数,两天复习英语,看到英语成绩是胸口突然好痛,好难受……就为了成为ACM正式队 ...

  7. 回归——继续我的ACM之路!!

    回归啦~~18年省赛结束后第一次参赛拿到了省级银牌对我是一个很大的鼓励,这是所感兴趣的事,我能做的不错,也就不愧于心了. 修整了两周多左右,建了建模,和阔爱的对象狂了两周,终于要静下来了,静下来一想, ...

  8. ACM之路

    从10月我刚接触到acm竞赛,到现在2017年2.20接近4个月的时间,我才刷到200道题.在刷题的过程中,我曾遇到困难,我也从一次性就a过,但是有时候会想到放弃.不过既然已经踏进来一只脚,还不如就好 ...

  9. [ASP.NET MVC 小牛之路]18 - Web API

    Web API 是ASP.NET平台新加的一个特性,它可以简单快速地创建Web服务为HTTP客户端提供API.Web API 使用的基础库是和一般的MVC框架一样的,但Web API并不是MVC框架的 ...

随机推荐

  1. MySql 8.0.11 客户端连接失败:2059 - Authentication plugin 'caching_sha2_password' cannot be loaded: ....

    近期,换了新笔记本,重新安装了MySql数据库和客户端工具Navicat Premium 12.我是从官网上下载的MySql数据库,版本为8.0.11,链接:https://dev.mysql.com ...

  2. git bash配置SSH远程连接阿里云ECS

    1.连接配置 1-1.添加安全组规则 1-2.使用GitHub的话本地都会有id_rsa.pub(公钥),id_rsa(私钥),一般保存在C盘用户目录下.ssh文件夹. 把公钥内容复制下来(ssh-r ...

  3. Django rest-framework框架-版本控制

    第一版: from rest_framework.versioning import BaseVersioning class ParamVersion(object): def determine_ ...

  4. javaIO——LineNumberReader

    LineNumberReader 是java字符流中的一员,它继承自 BufferedReader,只是在 BufferedReader 基础上,提供了对当前流位置所在文本行的标记记录.先来看看定义: ...

  5. LeetCode 腾讯精选50题--只出现一次数字

    事先说明,如果不是评论区的大牛一语点破,我可能还会陷在死胡同里出不来,这道题其实很简单,利用了任何一个学过二进制的人都了解的定理,即: 1. 异或操作满足交换律 : a ^ b ^ c 等价于 a ^ ...

  6. php 限制标题长度,将一个中文转换成一个字符

    点击链接加入群[php/web 学习课堂]:https://jq.qq.com/?_wv=1027&k=5UJ9vEa 欢迎大家加入,一起讨论学习 玩这个功能的时候,我们要注意一点,我们是用中 ...

  7. Nginx安装与配置【转】

    原文:linux之nginx 作者;海燕. 一.nginx Ngix是web服务器,跟apache一样,它可以做动态请求转发.web端负载均衡.反向代理等等: tomcat是应用服务器,当然如果非用逼 ...

  8. 【python+beautifulsoup4】Python中安装bs4后,pycharm报错ModuleNotFoundError: No module named 'bs4'

    本文主要分享关于在对应python版本中安装beautifulsoup之后,在代码执行时还会提示“No module named 'bs4'”的问题. 安装beautifsoup4 在命令窗口执行 p ...

  9. 13_Hive优化

    Hive优化 要点:优化时,把hive sql当做map reduce程序来读,会有意想不到的惊喜. 理解hadoop的核心能力,是hive优化的根本. 长期观察hadoop处理数据的过程,有几个显著 ...

  10. NOI2018 你的名字——SAM+线段树合并

    题目链接在这里洛谷/LOJ 题目大意 有一个串\(S\),每次询问给你一个串\(T\),两个数\(L\)和\(R\),问你\(T\)有多少个本质不同的子串不是\(S[L,R]\)的子串 SOLUTIO ...