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在这里指的是同阶的单位矩阵。代码如下(也可以直接当做矩阵的模板了):

  1 #include <stdio.h>
  2 #include <algorithm>
  3 #include <math.h>
  4 #include <vector>
  5 #include <map>
  6 #include <set>
  7 #include <iostream>
  8 #include <string.h>
  9 #pragma comment(linker, "/STACK:1024000000,1024000000")
 10 using namespace std;
 11 typedef long long ll;
 12 typedef pair<int,int> pii;
 13 const int mod = 10;
 14 
 15 //int mod;
 16 
 17 void add(int &a,int b)
 18 {
 19     a += b;
 20     if(a < 0) a += mod;
 21     //if(a >= mod) a -= mod;
 22     a %= mod;
 23 }
 24 
 25 struct matrix
 26 {
 27     int e[50+5][50+5],n,m;
 28     matrix() {}
 29     matrix(int _n,int _m): n(_n),m(_m) {memset(e,0,sizeof(e));}
 30     matrix operator * (const matrix &temp)const
 31     {
 32         matrix ret = matrix(n,temp.m);
 33         for(int i=1;i<=ret.n;i++)
 34         {
 35             for(int j=1;j<=ret.m;j++)
 36             {
 37                 for(int k=1;k<=m;k++)
 38                 {
 39                     add(ret.e[i][j],1LL*e[i][k]*temp.e[k][j]%mod);
 40                 }
 41             }
 42         }
 43         return ret;
 44     }
 45     matrix operator + (const matrix &temp)const
 46     {
 47         matrix ret = matrix(n,m);
 48         for(int i=1;i<=n;i++)
 49         {
 50             for(int j=1;j<=m;j++)
 51             {
 52                 add(ret.e[i][j],(e[i][j]+temp.e[i][j])%mod);
 53             }
 54         }
 55         return ret;
 56     }
 57     void getE()
 58     {
 59         for(int i=1;i<=n;i++)
 60         {
 61             for(int j=1;j<=m;j++)
 62             {
 63                 e[i][j] = i==j?1:0;
 64             }
 65         }
 66     }
 67 };
 68 
 69 matrix qpow(matrix temp,int x)
 70 {
 71     int sz = temp.n;
 72     matrix base = matrix(sz,sz);
 73     base.getE();
 74     while(x)
 75     {
 76         if(x & 1) base = base * temp;
 77         x >>= 1;
 78         temp = temp * temp;
 79     }
 80     return base;
 81 }
 82 
 83 void print(matrix p)
 84 {
 85     int n = p.n;
 86     int m = p.m;
 87     for(int i=1;i<=n;i++)
 88     {
 89         for(int j=1;j<=m;j++)
 90         {
 91             printf("%d ",p.e[i][j]);
 92         }
 93         cout << endl;
 94     }
 95 }
 96 
 97 matrix solve(matrix a, int k)
 98 {
 99     if(k == 1) return a;
100     int n = a.n;
101     matrix temp = matrix(n,n);
102     temp.getE();
103     if(k & 1)
104     {
105         matrix ex = qpow(a,k);
106         k--;
107         temp = temp + qpow(a,k/2);
108         return temp * solve(a,k/2) + ex;
109     }
110     else
111     {
112         temp = temp + qpow(a,k/2);
113         return temp * solve(a,k/2);
114     }
115 }
116 
117 int main()
118 {
119     int n,k;
120     while(scanf("%d%d",&n,&k)==2 && n)
121     {
122         matrix a = matrix(n,n);
123         for(int i=1;i<=n;i++)
124         {
125             for(int j=1;j<=n;j++) {scanf("%d",&a.e[i][j]);a.e[i][j] %= mod;} 
126             // 这里矩阵的每个元素在输入以后就要mod一下,不然可能会因为输入的元素太大导致在后面爆int(?)
127         }
128         matrix ans = solve(a,k);
129         for(int i=1;i<=n;i++)
130         {
131             for(int j=1;j<=n;j++)
132             {
133                 printf("%d%c",ans.e[i][j],j==n?'
':' ');
134             }
135         }
136         puts("");
137     }
138 }
矩阵模板

  还要讲的几点是: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题。这题的代码如下:

  1 #include <stdio.h>
  2 #include <algorithm>
  3 #include <math.h>
  4 #include <vector>
  5 #include <map>
  6 #include <set>
  7 #include <iostream>
  8 #include <string.h>
  9 #pragma comment(linker, "/STACK:1024000000,1024000000")
 10 using namespace std;
 11 typedef pair<int,int> pii;
 12 //const int mod = 1000;
 13 
 14 int n,mod,d,k;
 15 
 16 void add(int &a,int b)
 17 {
 18     a += b;
 19     if(a < 0) a += mod;
 20     //if(a >= mod) a -= mod;
 21     a %= mod;
 22 }
 23 
 24 struct matrix
 25 {
 26     int e[5][500+5],n,m;
 27     matrix() {}
 28     matrix(int _n,int _m): n(_n),m(_m) {memset(e,0,sizeof(e));}
 29     matrix operator * (const matrix &temp)const
 30     {
 31         matrix ret = matrix(1,m);
 32         for(int i=0;i<m;i++)
 33         {
 34             for(int j=0;j<m;j++)
 35             {
 36                 add(ret.e[0][i],1LL*e[0][j]*temp.e[0][((j-i)%m+m)%m]%mod);
 37             }
 38         }
 39         return ret;
 40     }
 41 
 42 };
 43 
 44 matrix qpow(matrix temp,int x)
 45 {
 46     int sz = temp.m;
 47     matrix base = matrix(1,sz);
 48     base.e[0][0] = 1;
 49     while(x)
 50     {
 51         if(x & 1) base = base * temp;
 52         x >>= 1;
 53         temp = temp * temp;
 54     }
 55     return base;
 56 }
 57 
 58 void print(matrix p)
 59 {
 60     int n = p.n;
 61     int m = p.m;
 62     for(int i=0;i<n;i++)
 63     {
 64         for(int j=0;j<m;j++)
 65         {
 66             printf("%d ",p.e[i][j]);
 67         }
 68         cout << endl;
 69     }
 70 }
 71 
 72 int main()
 73 {
 74     while(scanf("%d%d%d%d",&n,&mod,&d,&k)==4)
 75     {
 76         matrix ans = matrix(1,n);
 77         for(int i=0;i<n;i++) scanf("%d",&ans.e[0][i]);
 78         matrix temp = matrix(1,n);
 79         //for(int i=0;i<n;i++)
 80         {
 81             temp.e[0][0]=1;
 82             for(int j=1;j<=d;j++)
 83             {
 84                 int now = 0+j;
 85                 if(now >= n) now -= n;
 86                 temp.e[0][now] = 1;
 87             }
 88             for(int j=1;j<=d;j++)
 89             {
 90                 int now = 0-j;
 91                 if(now < 0) now += n;
 92                 temp.e[0][now] = 1;
 93             }
 94         }
 95         //print(temp);
 96         temp = qpow(temp,k);
 97         ans = ans * temp;
 98         for(int i=0;i<n;i++)
 99         {
100             printf("%d%c",ans.e[0][i],i==n-1?'
':' ');
101         }
102     }
103 }
循环矩阵

要注意之所以这份代码里面的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,不能直接取余,要结合数论中的知识来,如下:

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

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

  1 #include <stdio.h>
  2 #include <algorithm>
  3 #include <math.h>
  4 #include <vector>
  5 #include <map>
  6 #include <set>
  7 #include <iostream>
  8 #include <string.h>
  9 using namespace std;
 10 typedef long long ll;
 11 typedef pair<int,int> pii;
 12 //const int mod = 1e9 + 7;
 13 
 14 int mod;
 15 
 16 void add(int &a,int b)
 17 {
 18     a += b;
 19     if(a < 0) a += mod;
 20     //if(a >= mod) a -= mod;
 21     a %= mod;
 22 }
 23 
 24 struct matrix
 25 {
 26     int e[10][10];
 27     int n,m;
 28     matrix() {}
 29     matrix(int _n,int _m): n(_n),m(_m) {memset(e,0,sizeof(e));}
 30     matrix operator * (const matrix &temp)const
 31     {
 32         matrix ret = matrix(n,temp.m);
 33         for(int i=1;i<=ret.n;i++)
 34         {
 35             for(int j=1;j<=ret.m;j++)
 36             {
 37                 for(int k=1;k<=m;k++)
 38                 {
 39                     add(ret.e[i][j],1LL*e[i][k]*temp.e[k][j]%mod);
 40                 }
 41             }
 42         }
 43         return ret;
 44     }
 45     matrix operator + (const matrix &temp)const
 46     {
 47         matrix ret = matrix(n,m);
 48         for(int i=1;i<=n;i++)
 49         {
 50             for(int j=1;j<=m;j++)
 51             {
 52                 add(ret.e[i][j],(e[i][j]+temp.e[i][j])%mod);
 53             }
 54         }
 55         return ret;
 56     }
 57     void getE()
 58     {
 59         for(int i=1;i<=n;i++)
 60         {
 61             for(int j=1;j<=m;j++)
 62             {
 63                 e[i][j] = i==j?1:0;
 64             }
 65         }
 66     }
 67 };
 68 
 69 matrix qpow(matrix temp,int x)
 70 {
 71     int sz = temp.n;
 72     matrix base = matrix(sz,sz);
 73     base.getE();
 74     while(x)
 75     {
 76         if(x & 1) base = base * temp;
 77         x >>= 1;
 78         temp = temp * temp;
 79     }
 80     return base;
 81 }
 82 
 83 void print(matrix p)
 84 {
 85     int n = p.n;
 86     int m = p.m;
 87     for(int i=1;i<=n;i++)
 88     {
 89         for(int j=1;j<=m;j++)
 90         {
 91             printf("%d ",p.e[i][j]);
 92         }
 93         cout << endl;
 94     }
 95 }
 96 
 97 void Set(matrix &temp,int x,int y,int op)
 98 {
 99     temp.e[x][y] = op;
100 }
101 
102 matrix solve(matrix temp,int x)
103 {
104     if(x==1) return temp;
105     matrix ret = matrix(2,2);
106     ret.getE();
107     if(x & 1)
108     {
109         return (ret+qpow(temp,x/2)) * solve(temp,x/2) + qpow(temp,x);
110     }
111     else
112     {
113         return (ret+qpow(temp,x/2)) * solve(temp,x/2);
114     }
115 }
116 
117 void getAns(int r) {
118     //printf("Yes
");
119     int ans[205][205];
120     memset(ans, -1, sizeof(ans));
121     for (int i = r / 2 + 1; i <= r; i++)
122         ans[i][i - (r / 2)] = 0;
123     for (int i = r / 2 + 2; i <= r; i++)
124         for (int j = 1; j <= (i - r / 2 - 1); j++)
125             ans[i][j] = 1;
126     for (int i = r / 2 + 1; i <= r; i++)
127         for (int j = r - i + 1; j <= r; j++)
128             ans[j][i] = 1;
129     for (int i = 1; i <= r; i++) {
130         for (int j = 1; j <= r; j++) {
131             printf("%d", ans[i][j]);
132             printf(j == r ? "
" : " ");
133         }
134     }
135 }
136 
137 int main()
138 {
139     /*
140         http://www.cnblogs.com/Torrance/p/5412802.html
141     */
142     
143     int n;
144     int T;cin >>T;
145     for(int kase=1;kase<=T;kase++)
146     {
147         scanf("%d%d",&n,&mod);
148         printf("Case %d: ",kase);
149         /*matrix ans = matrix(1,2);
150         ans.e[1][1] = ans.e[1][2] = 1;
151 
152         matrix temp = matrix(2,2);
153         temp.e[1][2] = temp.e[2][1] = temp.e[2][2] = 1;
154         temp = solve(temp,n-1);
155 
156         matrix t = matrix(2,2);t.getE();
157         temp = temp + t;
158 
159         ans = ans * temp;
160         int sum = ans.e[1][1];*/
161 
162         // 上面的方法是这样的:
163         /*
164             (F1,F2)* temp(构造矩阵) = (F2,F3)
165             同理的:(F1,F2)* temp^2 = (F3,F4)
166             ...
167             那么只要(F1,F2)*(temp^0+temp^1+...+temp^(n-1)),这个矩阵的第一个元素即是sum
168             这是矩阵的分配律。而后面部分只要二分即可求得
169         */
170 
171         // 下面是常规的方法(?)
172         /*
173             (F1,F2,sum(初始化为F1))*temp^(n-1)
174             sum可以理解为加到F1(第一个元素)为止的元素的和,第二个元素可以理解为当前要加的元素
175             temp为:
176             0 1 0
177             1 1 1
178             0 0 1
179         */
180 
181         matrix ans = matrix(1,3);
182         for(int i=1;i<=3;i++) Set(ans,1,i,1);
183         matrix temp = matrix(3,3);
184         Set(temp,1,2,1);Set(temp,2,1,1);Set(temp,2,2,1);Set(temp,2,3,1);Set(temp,3,3,1);
185 
186         temp = qpow(temp,n-1);
187         ans = ans * temp;
188         int sum = ans.e[1][3];
189 
190         //cout << sum <<endl;
191 
192         cout << (sum%2 || sum ==0 ? "No":"Yes") <<endl;
193         if(sum % 2 == 0 && sum != 0) getAns(sum);
194     }
195 }
View Code

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题代码如下:

 1 #include <stdio.h>
 2 #include <algorithm>
 3 #include <math.h>
 4 #include <vector>
 5 #include <map>
 6 #include <set>
 7 #include <iostream>
 8 #include <string.h>
 9 using namespace std;
10 typedef pair<int,int> pii;
11 const int mod = 6;
12 
13 int n,k;
14 
15 typedef vector<int> vec ;
16 typedef vector<vec> mat ;
17 
18 inline void add (int &a ,int b) {
19     a += b ; if(a<0) a+=mod; a%=mod ;
20 }
21 
22 mat mul (const mat &a , const mat &b) {
23     mat ret(a.size(),vec(b[0].size())) ;
24     for (int i=0 ; i<a.size() ; i++)
25         for (int j=0 ; j<b[0].size() ; j++)
26             for (int z=0 ; z<b.size() ; z++)
27                 add(ret[i][j],a[i][z]*b[z][j]%mod) ;
28     return ret ;
29 }
30 
31 int main () {
32     while(scanf("%d%d",&n,&k)==2)
33     {
34         if(n==0 && k==0) break;
35         mat a(n,vec(k));
36         mat b(k,vec(n));
37         for(int i=0;i<n;i++)
38         {
39             for(int j=0;j<k;j++) scanf("%d",&a[i][j]);
40         }
41         for(int i=0;i<k;i++)
42         {
43             for(int j=0;j<n;j++) scanf("%d",&b[i][j]);
44         }
45         mat c =mul(b,a);
46         mat base(c.size(),vec(c.size()));
47         for(int i=0;i<c.size();i++) base[i][i] = 1;
48         /*
49             如果直接算A*B的话,得到的是1000*1000的矩阵,所以会一直超时。
50             因为C^(n*n)=(A*B)^(n*n)=A*(B*A)^(n*n-1)*B,由于B*A是6*6的矩阵,再用矩阵快速幂来求就行了。
51         */
52         int temp = n*n-1;
53         while(temp)
54         {
55             if(temp & 1) base = mul(base,c);
56             temp >>= 1;
57             c = mul(c,c);
58         }
59         mat ans = mul(mul(a,base),b);
60         int sum = 0;
61         for(int i=0;i<n;i++)
62         {
63             for(int j=0;j<n;j++) sum += ans[i][j];
64         }
65         cout << sum << endl;
66     }
67     return 0 ;
68 }
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的情况。

原文地址:https://www.cnblogs.com/zzyDS/p/5705288.html