ACM之路(18)—— 矩阵
矩阵是干什么的呢?一句话来说就是,知道相邻两个函数的递推关系和第一个数,让你递推到第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)—— 矩阵的更多相关文章
- nyist 606 ACM之路
http://acm.nyist.net/JudgeOnline/problem.php?pid=606 ACM之路 时间限制:1000 ms | 内存限制:65535 KB 描述 转眼间,12级新生 ...
- “玲珑杯”ACM比赛 Round #18
“玲珑杯”ACM比赛 Round #18 Start Time:2017-07-15 12:00:00 End Time:2017-07-15 15:46:00 A -- 计算几何你瞎暴力 Time ...
- 另一个ACM之路建议
ACM联系建议 一位高手对我的建议: 一般要做到50行以内的程序不用调试.100行以内的二分钟内调试成功.acm主要是考算法的 ,主要时间是花在思考算法上,不是花在写程序与debug上. 下面给个计划 ...
- 剪辑的楼天城的ACM之路
楼天城楼教主的acm心路历程(剪辑) 利用假期空闲之时,将这几年GCJ,ACM,TopCoder 参加的一些重要比赛作个回顾.昨天是GCJ2006 的回忆,今天时间上更早一些吧,我现在还清晰记得3 年 ...
- ACM之路(转载)
转载自:https://www.cnblogs.com/tianjintou/p/4139416.html 要注意,ACM的竞赛性强,因此自己应该和自己的实际应用联系起来. 适合自己的才是好的,有的人 ...
- ACM之路——上车了
校赛坚持到底,拿到了银牌:第一批进入ACM队集训,期末考试之前仍然代码不断,甚至感觉对不起大学第一次的期末考试,五天复习高数,两天复习英语,看到英语成绩是胸口突然好痛,好难受……就为了成为ACM正式队 ...
- 回归——继续我的ACM之路!!
回归啦~~18年省赛结束后第一次参赛拿到了省级银牌对我是一个很大的鼓励,这是所感兴趣的事,我能做的不错,也就不愧于心了. 修整了两周多左右,建了建模,和阔爱的对象狂了两周,终于要静下来了,静下来一想, ...
- ACM之路
从10月我刚接触到acm竞赛,到现在2017年2.20接近4个月的时间,我才刷到200道题.在刷题的过程中,我曾遇到困难,我也从一次性就a过,但是有时候会想到放弃.不过既然已经踏进来一只脚,还不如就好 ...
- [ASP.NET MVC 小牛之路]18 - Web API
Web API 是ASP.NET平台新加的一个特性,它可以简单快速地创建Web服务为HTTP客户端提供API.Web API 使用的基础库是和一般的MVC框架一样的,但Web API并不是MVC框架的 ...
随机推荐
- 选项卡TAB
一.基础信息 关键class名:nav 写法: (1)头部选中状态:class="active" (2)头部按钮进行切换:<a>加data-toggle="t ...
- 谈谈对Spring IOC的理解(转发)
学习过Spring框架的人一定都会听过Spring的IoC(控制反转) .DI(依赖注入)这两个概念,对于初学Spring的人来说,总觉得IoC .DI这两个概念是模糊不清的,是很难理解的,今天和大家 ...
- Shiro——入门Demo
Shiro——入门Demo 环境- 引入相关maven依赖, shiro-core,commons-logging 配置shiro配置文件:ini后缀 主方法测试: import org.apach ...
- python计算出现错误
用python计算39.8-0.1得出的结果是39.699999999999996 其他数字计算正确,唯独这个计算错误. 原因: 中文解释: https://docs.python.org/zh-cn ...
- JS实现旋转的魔方
js <script> window.onload = function () { let cube = document.querySelector('.cube') let timer ...
- footer始终在页面最底部的方法(问题待检验)
一.css方法 <style type="text/css"> html,body{ height: 100%; } body{ display: flex; flex ...
- Lab1 Report
Lab1 report A) The brief description that you install junit, hamcrest and eclemma. a) install junit ...
- 如何用Visual Studio Code远程调试运行在服务器上的nodejs应用
假设我有一个nodejs应用,运行在AWS - 亚马逊云平台上(Amazone Web Service).我想用本地的Visual Studio Code来远程调试服务器端的nodejs应用. Vis ...
- mount的bind选项
mount 的 bind 选项将第一个目录克隆到第二个.一个目录中的改变将会在另一个中出现 - 毕竟,它是同一磁盘上的同一个块. 使用 bind 与对同一设备进行两次挂载的区别在于:您可以挂载子目 ...
- K8S搭建过程随笔_系统初始化
组件 Kubernetes 1.14.2 Docker 18.09.6-ce Etcd 3.3.13 Flanneld 0.11.0 基础环境设置 192.168.11.188 k8s-master ...