哈哈哈,博主又回来了!这次专题是第三弹也是最后一弹了,这次会对矩阵进行一个小收尾。做完这25道题,我感觉到其实我矩阵学得并不好,还有许多知识点没有学会。后面看情况可能还会继续开矩阵的专题,那应该是几个月以后的事了。从下周开始,应该会先学习一下数论的相关算法!
这次的七道题目(为什么题目越来越少了)主要是针对了矩阵的优化,对于会TLE的和MLE(内存爆了)的矩阵而且这个矩阵又恰好是同构矩阵(同构矩阵是啥?)的话,可以采用一维数组来模拟二维,从而降低复杂度、降低空间。(竟然是罕见的同时降时间和空间的做法,但是条件有点苛刻=、=)。
第一题 Fzu-1692
分析:首先要告知读者题目有错误(坑啊!),题目中的( L*A(i+n-1)%n+R*A(i+1)%n )应该改为( R*A(i+n-1)%n+L*A(i+1)%n ),就是把L和R交换一下位置,不明白为什么福州大学不改掉,都这么长的时间了。
对于这种换苹果、换火柴的问题,做的太多,感觉一下子就有思路,而且可以说题目上已经把递推式给出了。
对于A[i]来说,一次变换后A[i']=R*A[(i+n-1)%n]+A[i]+L*A[(i+1)%n]
构造矩阵
但是呢,很可惜的是这个算法会超时!我们观察一下矩阵的形式,发现我们构造出来的矩阵有些特殊,每一行与它相邻的一行之间位置关系只差了1个位置(似乎就叫同构矩阵),是一种循环的关系。那么我们根据线性代数的乱七八糟的性质(我不记得了)就可以推到两个同构矩阵相乘得到的矩阵仍然是同构矩阵,那么我们是不是就可以简化运算,只需求结果矩阵的第一行,然后后面的每一行都可以根据前一行与其位置差1的关系,直接推出来,不必一一运算了。
#include <map> #include <set> #include <stack> #include <cmath> #include <queue> #include <bitset> #include <string> #include <vector> #include <cstdio> #include <cctype> #include <cstdlib> #include <sstream> #include <cstring> #include <iostream> #include <algorithm> #pragma comment(linker, "/STACK:1024000000,1024000000") using namespace std; #define clr(x,y) memset(x,y,sizeof(x)) #define rep(i,n) for(int i=0;i<(n);i++) #define repf(i,a,b) for(int i=(a);i<=(b);i++) #define maxn 100+5 #define lson l,m,rt<<1 #define rson m+1,r,rt<<1|1 #define IT iterator #define push_back PB typedef long long ll; const double eps = 1e-10; const double pi = acos(-1); const ll mod = 1e9+7; const int inf = 0x3f3f3f3f; ll M; struct matrix { int n; ll maze[maxn][maxn]; void init(int n) { this->n=n; clr(maze,0); } matrix operator * (const matrix & rhs) { matrix ans; ans.init(n); rep(j,n) rep(k,n) ans.maze[0][j]=(ans.maze[0][j]+maze[0][k]*rhs.maze[k][j])%M; repf(i,1,n-1) rep(j,n) ans.maze[i][j]=ans.maze[i-1][(j+n-1)%n]; return ans; } }; matrix qlow(matrix a,ll n) { matrix ans; ans.init(a.n); rep(i,a.n)ans.maze[i][i]=1; while(n) { if(n&1)ans=ans*a; a=a*a; n>>=1; } return ans; } int main() { //freopen("d:\\acm\\in.in","r",stdin); int t; scanf("%d",&t); while(t--) { int n,L,R; ll m; scanf("%d %I64d %d %d %lld",&n,&m,&L,&R,&M); matrix ans; ans.init(n); rep(i,n)scanf("%lld",&ans.maze[0][i]); matrix ant; ant.init(n); rep(i,n) { ant.maze[i][i]=1; ant.maze[(i+n-1)%n][i]=R; ant.maze[(i+1)%n][i]=L; } ant=qlow(ant,m); ans=ans*ant; int flag=0; rep(i,n) { if(flag)putchar(' '); flag=1; printf("%lld",ans.maze[0][i]); } putchar('\n'); } return 0; }
分析:这是一道非常有意思的题目,它在构造矩阵上没有什么难度,递推都已经给出了。但是这道题目难在g(n)迭代了三次,在里层是不可以取1e9+7的模的。这里需要使用循环节,循环节通俗的说就是按照斐波那契式的递推(a(n)=b1*a(n-1)+b2*a(n-2)+......)并且取模的话,一定会经历一个循环段然后会回到开头的状态(a(XXX)=a1,a(XXX+1)=a2,a(XXX+3)=a3,.......这里XXX-1就是循环节),至于证明我就不证明了(其实我也不会证明,大家知道就好了,在这种情况下循环节是一定存在的)。那么我们就可以一层一层的求循环节了,每一层使用对应的循环节取模,最外层用1e9+7。
至于为什么正确的话,可以考虑a(XXX)=a1,那么a(XXX%(XXX-1))=a(1)=a1,是不是内层的取模与外层的取模相一致。
这里给出求循环节的代码(其实就是暴力)
ll m = 1e9+7; //222222224; //183120 int main() { //freopen("d:\\acm\\in.in","r",stdin); ll a=0,b=1; ll t; for(int i=1;;i++) { t=(a+3*b)%m; a=b; b=t; if(a==0&&b==1) { cout<<i<<endl; break; } } return 0; } //222222224 //183120 //240下我们得到了四个循环节(240那个是我打着玩的,其实不要)
#include <map> #include <set> #include <stack> #include <cmath> #include <queue> #include <bitset> #include <string> #include <vector> #include <cstdio> #include <cctype> #include <cstdlib> #include <sstream> #include <cstring> #include <iostream> #include <algorithm> #pragma comment(linker, "/STACK:1024000000,1024000000") using namespace std; #define clr(x,y) memset(x,y,sizeof(x)) #define rep(i,n) for(int i=0;i<(n);i++) #define repf(i,a,b) for(int i=(a);i<=(b);i++) #define maxn 0+5 #define lson l,m,rt<<1 #define rson m+1,r,rt<<1|1 #define IT iterator #define push_back PB typedef long long ll; const double eps = 1e-10; const double pi = acos(-1); const ll mod = 1e9+7; const int inf = 0x3f3f3f3f; ll m[3]={183120,222222224,1e9+7}; int ca; struct matrix { int n; ll maze[maxn][maxn]; void init(int n) { this->n=n; clr(maze,0); } matrix operator * (const matrix& rhs) { matrix ans; ans.init(n); rep(i,n) rep(j,n) rep(k,n) ans.maze[i][j]=(ans.maze[i][j]+maze[i][k]*rhs.maze[k][j])%m[ca]; return ans; } }; matrix qlow(matrix a,ll n) { matrix ans; ans.init(a.n); rep(i,a.n)ans.maze[i][i]=1; while(n) { if(n&1)ans=ans*a; a=a*a; n>>=1; } return ans; } int main() { //freopen("d:\\acm\\in.in","r",stdin); ll n; while(scanf("%I64d",&n)!=EOF) { n%=240; for(ca=0;ca<3;ca++) { if(n<2)break; matrix ans; ans.init(2); ans.maze[1][0]=ans.maze[0][1]=1; ans.maze[1][1]=3; ans=qlow(ans,n-1); n=ans.maze[1][1]; } printf("%I64d\n",n); } return 0; }
其实我们可以看出来这个最里层的n其实是以240为循环(只有240种不同的结果)这么少的结果,正好打表!
#include <map> #include <set> #include <stack> #include <cmath> #include <queue> #include <bitset> #include <string> #include <vector> #include <cstdio> #include <cctype> #include <fstream> #include <cstdlib> #include <sstream> #include <cstring> #include <iostream> #include <algorithm> #pragma comment(linker, "/STACK:1024000000,1024000000") using namespace std; #define clr(x,y) memset(x,y,sizeof(x)) #define rep(i,n) for(int i=0;i<(n);i++) #define repf(i,a,b) for(int i=(a);i<=(b);i++) #define maxn 0+5 #define lson l,m,rt<<1 #define rson m+1,r,rt<<1|1 #define IT iterator #define push_back PB typedef long long ll; const double eps = 1e-10; const double pi = acos(-1); const ll mod = 1e9+7; const int inf = 0x3f3f3f3f; int dat[240]={0,1,42837,301657676,293732885,490640373,33742025,164692016,462308703,52762375,343131756,21405314,237068195,596899309,906139148,211536041,366655491,896628168,66227266,72120020,402845524,142832152,798631844,624391292,206125778,943175270,640019193,303259648,182635905,579125043,579433543,83927026,717557117,40368430,863093688,335922744,512468934,813575940,844062243,707187631,278519020,670147186,933772741,378841811,627788085,258191260,844841005,802471932,662733064,781240855,676645643,236653810,511827197,880889921,799930429,42383237,938628882,679827669,909921493,863143716,347415371,427513662,372282604,576747399,938628882,839037397,33742025,420342938,835920182,193194645,57151287,416852020,337266943,547930942,487542115,764567815,676186842,326357106,772837852,284929959,278519020,473357016,682900754,316947814,153532664,804007392,859774430,848575784,717557117,50843067,579433543,857877224,164332999,157021226,414571236,568476330,793874229,136594603,693461119,491013304,138865244,804171493,227162155,610297485,366655491,248827358,293577661,347255110,998948656,859995852,375475523,809290428,462308703,118039786,799930429,339244061,247992086,78659144,503609964,396857679,0,396857679,503609964,78659144,247992086,339244061,200069578,118039786,462308703,809290428,375475523,859995852,1051351,347255110,293577661,248827358,366655491,610297485,772837852,804171493,138865244,491013304,693461119,136594603,206125778,568476330,414571236,157021226,164332999,857877224,420566464,50843067,717557117,848575784,859774430,804007392,846467343,316947814,682900754,473357016,278519020,284929959,227162155,326357106,676186842,764567815,487542115,547930942,662733064,416852020,57151287,193194645,835920182,420342938,966257982,839037397,938628882,576747399,372282604,427513662,652584636,863143716,909921493,679827669,938628882,42383237,200069578,880889921,511827197,236653810,676645643,781240855,337266943,802471932,844841005,258191260,627788085,378841811,66227266,670147186,278519020,707187631,844062243,813575940,487531073,335922744,863093688,40368430,717557117,83927026,420566464,579125043,182635905,303259648,640019193,943175270,793874229,624391292,798631844,142832152,402845524,72120020,933772741,896628168,366655491,211536041,906139148,596899309,762931812,21405314,343131756,52762375,462308703,164692016,966257982,490640373,293732885,301657676,42837,1}; int main() { //freopen("d:\\acm\\in.in","r",stdin); ll n; while(~scanf("%lld",&n)) { cout<<dat[n%240]<<endl; } return 0; }
第三题 Uva-12470
分析:矩阵裸题,非常简单,题目已经给出了递推式,这里仅给出代码
#include<set> #include<map> #include<cmath> #include<stack> #include<queue> #include<vector> #include<iostream> #include<cstdio> #include<cstring> #include<string> #include<algorithm> #include<cctype> #define maxn 0+5 #define clr(x,y) memset(x,y,sizeof(x)) using namespace std; typedef long long ll; const int inf = 0x3f3f3f3f; const double pi = acos( -1 ); const ll mod = 1e9+9; const double eps = 1e-10; ll mul(ll a,ll b) { ll ans=0; while(b) { if(b&1)ans=(ans+a)%mod; a=(a+a)%mod; b>>=1; } return ans; } struct matrix { int n; ll maze[maxn][maxn]; void init(int n) { this->n=n; clr(maze,0); } matrix operator * (const matrix & rhs) { matrix ans; ans.init(n); for(int i=0;i<n;i++) for(int j=0;j<n;j++) for(int k=0;k<n;k++) ans.maze[i][j]=(ans.maze[i][j]+mul(maze[i][k],rhs.maze[k][j]))%mod; return ans; } }; matrix qlow(matrix a,ll n) { matrix ans; ans.init(a.n); for(int i=0;i<a.n;i++)ans.maze[i][i]=1; while(n) { if(n&1)ans=ans*a; a=a*a; n>>=1; } return ans; } int main() { //freopen("d:\\acm\\in.in","r",stdin); ll n; while(scanf("%lld",&n),n) { if(n<4) { printf("%lld\n",n-1); continue; } matrix ans; ans.init(3); ans.maze[0][1]=1; ans.maze[0][2]=2; matrix ant; ant.init(3); ant.maze[0][2]=ant.maze[1][0]=ant.maze[1][2]=ant.maze[2][1]=ant.maze[2][2]=1; ant=qlow(ant,n-3); ans=ans*ant; printf("%lld\n",ans.maze[0][2]); } return 0; }
分析:非常奇怪的一道题目,需要很大的脑洞。有些人通过打表查oeis发现了规律F(n)=3*F(n-1)-F(n-2),然后就可以愉快地矩阵快速幂了(其实这也不失为一种好方法,但是比赛的时候没有oeis怎么办,那只能从打表的前几个直接看出规律,说实话有点难啊)。
但是呢,还是有好办法的。从题意上看,可以看出组合数满足二项式定理的系数,那么我们看能不能凑到二项式定理上。考虑f(n)是否可以转化为A^n的形式,我们想起来以前构造过的求斐波那契的矩阵A=[ 1 1 | (换行) 1 0 ],看出可以用A^n来代替f(n),那么还有一项系数怎么办呢,不妨用E^n(E是单位矩阵)来填补空缺。那么题目就转化为了(A+E)^n,我们需要的答案就是结果矩阵的[0][1]或者[1][0]项。
#include <map> #include <set> #include <stack> #include <cmath> #include <queue> #include <bitset> #include <string> #include <vector> #include <cstdio> #include <cctype> #include <fstream> #include <cstdlib> #include <sstream> #include <cstring> #include <iostream> #include <algorithm> #pragma comment(linker, "/STACK:1024000000,1024000000") using namespace std; #define clr(x,y) memset(x,y,sizeof(x)) #define rep(i,n) for(int i=0;i<(n);i++) #define repf(i,a,b) for(int i=(a);i<=(b);i++) #define maxn 0+5 #define lson l,m,rt<<1 #define rson m+1,r,rt<<1|1 #define IT iterator #define push_back PB typedef long long ll; const double eps = 1e-10; const double pi = acos(-1); const ll mod = 1e9+7; const int inf = 0x3f3f3f3f; ll m; struct matrix { int n; ll maze[maxn][maxn]; void init(int n) { this->n=n; clr(maze,0); } matrix operator *(const matrix& rhs) { matrix ans; ans.init(n); rep(i,n) rep(j,n) rep(k,n) ans.maze[i][j]=(ans.maze[i][j]+maze[i][k]*rhs.maze[k][j])%m; return ans; } }; matrix qlow(matrix a,int n) { matrix ans; ans.init(a.n); rep(i,a.n)ans.maze[i][i]=1; while(n) { if(n&1)ans=ans*a; a=a*a; n>>=1; } return ans; } int main() { //freopen("d:\\acm\\in.in","r",stdin); int t; scanf("%d",&t); while(t--) { int n; scanf("%d %lld",&n,&m); matrix ans; ans.init(2); ans.maze[0][0]=2; ans.maze[0][1]=ans.maze[1][0]=ans.maze[1][1]=1; ans=qlow(ans,n); printf("%lld\n",ans.maze[0][1]); } return 0; }
分析:这类题目与之前的有些类似,先考虑转化的问题。假设前n个字符都已经符合条件了,我们把最后一个字符作为分类的基础(或者说是标志)。我一开始把以A~Z结尾和以a~z结尾分为两类,互相计算转化,但是在构造矩阵的时候发现不可以,因为A~Z转化到a~z的个数是不确定的,不能构造出固定的矩阵。那么我只能想到把所有字符各自的算作一类,那么矩阵就可以构造了。在写递推式的时候,我又遇到了困难,无法写出完全满足题目两个条件的式子。那么我考虑组合数学中最常见的方法,将所有的减去不满足的(取模对加减法满足分配律),先计算每相邻两个字符差值不大于32的所有种类(可以取模),再计算每相邻两个字符差值小于32的所有种类(可以取模),将前者减去后者,如果是负值的话加上一个mod。
#include <map> #include <set> #include <stack> #include <cmath> #include <queue> #include <bitset> #include <string> #include <vector> #include <cstdio> #include <cctype> #include <fstream> #include <cstdlib> #include <sstream> #include <cstring> #include <iostream> #include <algorithm> #pragma comment(linker, "/STACK:1024000000,1024000000") using namespace std; #define clr(x,y) memset(x,y,sizeof(x)) #define rep(i,n) for(int i=0;i<(n);i++) #define repf(i,a,b) for(int i=(a);i<=(b);i++) #define maxn 50+5 #define lson l,m,rt<<1 #define rson m+1,r,rt<<1|1 #define IT iterator #define push_back PB typedef long long ll; const double eps = 1e-10; const double pi = acos(-1); const ll mod = 1e9+7; const int inf = 0x3f3f3f3f; struct matrix { int n; ll maze[maxn][maxn]; void init(int n) { this->n=n; clr(maze,0); } matrix operator *(const matrix& rhs) { matrix ans; ans.init(n); rep(i,n) rep(j,n) rep(k,n) ans.maze[i][j]=(ans.maze[i][j]+maze[i][k]*rhs.maze[k][j])%mod; return ans; } }; matrix qlow(matrix a,int n) { matrix ans; ans.init(a.n); rep(i,a.n)ans.maze[i][i]=1; while(n) { if(n&1)ans=ans*a; a=a*a; n>>=1; } return ans; } int main() { //freopen("d:\\acm\\in.in","r",stdin); int t; scanf("%d",&t); while(t--) { int m; scanf("%d",&m); matrix ans; ans.init(52); rep(i,52) { if(i<26) rep(j,27+i) ans.maze[j][i]=1; else repf(j,i-26,51) ans.maze[j][i]=1; } ans=qlow(ans,m-1); ll anw=0; rep(i,52) rep(j,52) anw=(anw+ans.maze[i][j])%mod; ans.init(52); rep(i,52) { if(i<26) rep(j,26+i) ans.maze[j][i]=1; else repf(j,i-25,51) ans.maze[j][i]=1; } ans=qlow(ans,m-1); ll ant=0; rep(i,52) rep(j,52) ant=(ant+ans.maze[i][j])%mod; anw-=ant; if(anw<0)anw+=mod; printf("%lld\n",anw); } return 0; }
分析:这道题与之前做过的题目非常的相似,没有什么新意。根据n和d很简单的可以构造矩阵,因为d不同那么构造的矩阵不同,所以我就不画了。但是呢,n可以到500,非常大,一个时间复杂度上n^3就可能会TLE,而且内存上面可能会出现问题(我就是,明明感觉500*500的long long一点问题都没有,但是就是各种运行就崩溃)。所以要考虑矩阵的优化,看出这道题目中的矩阵满足循环矩阵的定义(定义是啥),那么我们使用一维数组来代替二维矩阵(前面说过了,循环矩阵每相邻两行之间的特殊关系,可以方便的简化运算,还可以方便的减小存储数据)。写法类似矩阵快速幂,就是把一维数组代替二维的进行运算,实在不会写就多写几个全局变量。有一个点需要注意,就是一维数组之间模拟矩阵相乘,之间有一个差量,是往前开始算的,我也讲不清楚,大家画画图,稍微模拟一下就懂了。
#include <map> #include <set> #include <stack> #include <cmath> #include <queue> #include <bitset> #include <string> #include <vector> #include <cstdio> #include <cctype> #include <fstream> #include <cstdlib> #include <sstream> #include <cstring> #include <iostream> #include <algorithm> #pragma comment(linker, "/STACK:1024000000,1024000000") using namespace std; #define clr(x,y) memset(x,y,sizeof(x)) #define rep(i,n) for(int i=0;i<(n);i++) #define repf(i,a,b) for(int i=(a);i<=(b);i++) #define maxn 500+5 #define lson l,m,rt<<1 #define rson m+1,r,rt<<1|1 #define IT iterator #define push_back PB typedef long long ll; const double eps = 1e-10; const double pi = acos(-1); const ll mod = 1e9+7; const int inf = 0x3f3f3f3f; int n; ll m; ll dat[maxn]; ll maze[maxn]; ll anw[maxn]; ll tmp[maxn]; void mul(ll a[],ll b[]) { rep(i,n) { tmp[i]=0; rep(j,n) tmp[i]=(tmp[i]+a[j]*b[(j+n-i)%n])%m; } rep(i,n) a[i]=tmp[i]; } void qlow(int n) { clr(anw,0); anw[0]=1; while(n) { if(n&1)mul(anw,maze); mul(maze,maze); n>>=1; } } int main() { //freopen("d:\\acm\\in.in","r",stdin); int d,k; while(~scanf("%d %lld %d %d",&n,&m,&d,&k)) { rep(i,n)scanf("%lld",&dat[i]); clr(maze,0); maze[0]=1; repf(i,1,d)maze[i]=maze[n-i]=1; qlow(k); mul(dat,anw); int flag=0; rep(i,n) { if(flag)putchar(' '); flag=1; printf("%lld",dat[i]); } putchar('\n'); } return 0; }
分析:感觉这道题就是用来测一测你的线性代数好不好,我很不好,所以做了很久。
分别分析三种操作的矩阵,语言讲不清,直接上图,[0][0]位置上是常数位1,a(n)表示第n只猫持有的花生数
对于g i 操作
对于s i j 操作
对于e i 操作
有了三种操作的对应矩阵,我就想把每个操作的构造矩阵都乘起来,那么就可以得到一次操作的矩阵,然后矩阵快速幂不是很简单吗!但是我又错了,这样的话显然是会TLE的,那么就还是要从矩阵的构造上下功夫,然后人家告诉我这些操作其实是什么基本操作(乱七八糟的,完全不记得了),可以直接在一个单位矩阵上面按照操作进行变换。然后,就没有然后了。g i 对应maze[0][i]++, s i j对应把maze的第i列和第j列完全交换,e i 对应把maze的第i列完全清零,最后矩阵快速幂!唉,数学差,真悲伤啊!
#include <map> #include <set> #include <stack> #include <cmath> #include <queue> #include <bitset> #include <string> #include <vector> #include <cstdio> #include <cctype> #include <fstream> #include <cstdlib> #include <sstream> #include <cstring> #include <iostream> #include <algorithm> #pragma comment(linker, "/STACK:1024000000,1024000000") using namespace std; #define clr(x,y) memset(x,y,sizeof(x)) #define rep(i,n) for(int i=0;i<(n);i++) #define repf(i,a,b) for(int i=(a);i<=(b);i++) #define maxn 100+5 #define lson l,m,rt<<1 #define rson m+1,r,rt<<1|1 #define IT iterator #define push_back PB typedef long long ll; const double eps = 1e-10; const double pi = acos(-1); const ll mod = 1e9+7; const int inf = 0x3f3f3f3f; struct matrix { int n; ll maze[maxn][maxn]; void init(int n) { this->n=n; clr(maze,0); } matrix operator* (const matrix& rhs) { matrix ans; ans.init(n); for(int i=0;i<n;i++) for(int j=0;j<n;j++) for(int k=0;k<n;k++) if(maze[i][k]&&rhs.maze[k][j]) ans.maze[i][j]+=maze[i][k]*rhs.maze[k][j]; return ans; } }; matrix qlow(matrix a,int n) { matrix ans; ans.init(a.n); for(int i=0;i<a.n;i++)ans.maze[i][i]=1; while(n) { if(n&1)ans=ans*a; a=a*a; n>>=1; } return ans; } int main() { //freopen("d:\\acm\\in.in","r",stdin); int n,m,k; while(scanf("%d %d %d",&n,&m,&k),n||m||k) { matrix ans; ans.init(n+1); for(int i=0;i<n+1;i++)ans.maze[i][i]=1; char op[5]; int a,b; while(k--) { scanf("%s",op); if(op[0]=='g') { scanf("%d",&a); ans.maze[0][a]++; } else if(op[0]=='s') { scanf("%d %d",&a,&b); for(int i=0;i<n+1;i++) swap(ans.maze[i][a],ans.maze[i][b]); } else { scanf("%d",&a); for(int i=0;i<n+1;i++) ans.maze[i][a]=0; } } ans=qlow(ans,m); int flag=0; for(int i=1;i<n+1;i++) { if(flag)putchar(' '); flag=1; printf("%lld",ans.maze[0][i]); } putchar('\n'); } return 0; }