NOI2013矩阵游戏

题目链接LUOGU1397矩阵游戏
题目大意
已知

f1,1=1fi,j=afi,j1+bfi,1=cfi1,m+d[j!=1][i1]

fn,m

算法一 矩阵快速幂

我们可以构造矩阵

[1fn,1]×[10ba]m1=[1fn,m]


[1cfn1,m+d]×[10ba]m1=[1fn,m]


T=[10ba]m1

x=T2,2

y=T1,2

fn,m=xcfn1,m+xd+y


[1f1,m]×[10xd+yxc]n1=[1fn,m]

所以我们可以先快速幂求出 f1,m 然后再快速幂求出 fn,m
但是由于 n,m 很大 n,m101000000 我们用高精度显然超时,而且非常不好打
因为 109+7 是一个质数,所以我们考虑费马小定理
ap11(modp)anan(modp1)(modp)

因为矩阵乘法也是满足费马小定理的
但是有一点问题(这里只举例说 2×2 的矩阵 n×n 的矩阵也是一样的)
[acbd]p1[1001]if ad(modp)[acbd]p[a00a][acbd]if a=dif ad(modp)

又在这道题中两个矩阵的 a 都为1
[1fn,1]×[10ba]m1=[1fn,1]×[10ba]m1(modp)[10ba]m1(modp1)if a=1if a1

所以我们可以直接取模,对于第二个矩阵快速幂也要判断一下 xc 是否等于 1
否则会被UOJ上的数据Hack掉(当然你第二个不判断也可以过官方数据,第一个不判你也有85分)

这里贴代码(代码有一些奇奇怪怪的东西 当然你可以无视)

#include<cstdio>
#include<cstring>
#define re register int
#define fp(i,a,b) for(re i=a,I=b;i<=I;++i)
#define file(s) freopen(s".in","r",stdin),freopen(s".out","w",stdout)
char ss[1<<17],*A=ss,*B=ss,ch;
inline char gc(){if(A==B){B=(A=ss)+fread(ss,1,1<<17,stdin);if(A==B)return EOF;}return*A++;}
template<class T>inline void sdf(T&x){
    while(ch=gc(),ch<48);x=ch^48;
    while(ch=gc(),48<=ch)x=10*x+ch-48;
}
inline void cis(char*s){
    while(ch=gc(),ch<48);*s++=ch;
    while(ch=gc(),48<=ch)*s++=ch;
}
char sr[1<<20],z[20];int C=-1,Z;
template<class T>inline void wer(T x){
    while(z[++Z]=x%10+'0',x/=10);
    while(sr[++C]=z[Z],--Z);
}
const int N=3,P=1e9+7;
typedef int arr[N];
typedef long long ll;
struct matrix{
    int a[N][N];
    inline int*operator[](re x){return a[x];}
    matrix(re x=0){memset(a,0,sizeof a);if(x==1)fp(i,0,N-1)a[i][i]=1;}
    inline matrix operator*(matrix b)const{
        matrix c;
        fp(k,1,2)fp(i,1,2)if(a[i][k])fp(j,1,2)
            c[i][j]=(c[i][j]+1ll*a[i][k]*b[k][j])%P;
        return c;
    }
    matrix operator^(ll b){
        matrix x(1),A=*this;
        for(;b;b>>=1,A=A*A)if(b&1)x=x*A;
        return x;
    }
}T;
ll ans=1,n,m,a,b,c,d,mod=P-1;char s1[1000010],s2[1000010];
int main(){
    file("oi");
    cis(s1);cis(s2);sdf(a);sdf(b);sdf(c);sdf(d);mod+=a==1;
    fp(i,0,strlen(s2)-1)m=(m*10+s2[i]-48)%mod;--m;if(m<0)m+=mod;
    T[2][2]=a;T[1][2]=b;T[1][1]=1;T=T^m;
    ans=(T[1][2]+T[2][2])%P;mod=P-1;
    T[1][2]=(1ll*T[2][2]*d+T[1][2])%P;
    T[2][2]=1ll*T[2][2]*c%P;mod+=T[2][2]==1;
    fp(i,0,strlen(s1)-1)n=(n*10+s1[i]-48)%mod;--n;if(n<0)n+=mod;
    T=T^n;ans=(T[1][2]+1ll*T[2][2]*ans)%P;wer(ans);
    fwrite(sr,1,C+1,stdout);
return 0;
}

算法二 数列通项公式

对于这种一阶递推式我们完全可以用等比数列的通项公式来求(反正上必修五老师会讲怎求的)注意接下来的运算都是带取模的,模数 P=109+7

fn,m=afn,m1+bfn,m={fn,1+b(m1)am1fn,1+(am11)ba1if a=1if a1x={1am1if a=1if a1y={b(m1)(am11)ba1if a=1if a1f1,m=xf1,1+yfn,m=x(cfn1,m+d)+y=xcfn1,m+xd+yp=xcq=xd+yfn,m=pfn,m1+q={f1,n+q(n1)pn1f1,m+(pn11)qp1if p=1if p1

同样的道理我们要用费马小定理来缩小 n,m
处理方式和矩阵的一样要分情况讨论
因为当 a=1 p=1 是这是等差数列而 gcd(a,P)=1,gcd(p,P)=1 所以循环长度是 P <script type="math/tex" id="MathJax-Element-34">P</script>
反之,我们就可以直接用快速幂运算了
这里贴代码

#include<cstdio>
#include<cstring>
#define re register int
#define rg register long long
#define fp(i,a,b) for(re i=a,I=b;i<=I;++i)
#define file(s) freopen(s".in","r",stdin),freopen(s".out","w",stdout)
char ss[1<<17],*A=ss,*B=ss,ch;
inline char gc(){if(A==B){B=(A=ss)+fread(ss,1,1<<17,stdin);if(A==B)return EOF;}return*A++;}
template<class T>inline void sdf(T&x){
    while(ch=gc(),ch<48);x=ch^48;
    while(ch=gc(),48<=ch)x=10*x+ch-48;
}
inline void cis(char*s){
    while(ch=gc(),ch<48);*s++=ch;
    while(ch=gc(),48<=ch)*s++=ch;
}
char sr[1<<20],z[20];int C=-1,Z;
template<class T>inline void wer(T x){
    while(z[++Z]=x%10+'0',x/=10);
    while(sr[++C]=z[Z],--Z);
}
const int N=3,P=1e9+7;
typedef int arr[N];
typedef long long ll;
ll ans,n,m,a,b,c,d,mod;char s1[1000010],s2[1000010];
inline ll powMod(rg a,rg b){
    rg x=1;
    for(;b;b>>=1,a=(a*a)%P)if(b&1)x=(x*a)%P;
    return x;
}
int main(){
    cis(s1);cis(s2);sdf(a);sdf(b);sdf(c);sdf(d);mod=P-(a!=1);
    fp(i,0,strlen(s2)-1)m=(m*10+s2[i]-48)%mod;--m;if(m<0)m+=mod;
    rg x=a==1?1:powMod(a,m)%P,y=a==1?b*m%P:(powMod(a,m)-1+P)*b%P*powMod(a-1,P-2)%P;
    ans=x+y;y=(x*d+y)%P;x=x*c%P;mod=P-(x!=1);
    fp(i,0,strlen(s1)-1)n=(n*10+s1[i]-48)%mod;--n;if(n<0)n+=mod;
    if(x==1)ans=(ans+n*y%P)%P;
    else ans=(ans*powMod(x,n)+(powMod(x,n)-1+P)*y%P*powMod(x-1,P-2))%P;
    wer(ans);
    fwrite(sr,1,C+1,stdout);
return 0;
}

这两份代码的复杂度都是一样的,主要卡在读入上。读者可以考虑把n%P和n%(P-1)都存下来,不要先存字符串。这样的话会快一些(纯粹是为了刷个排名吧)代码这里就不贴了,留给读者自己思考。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值