首页 > 代码库 > “玲珑杯”ACM比赛 Round #12 (D) 【矩阵快速幂的时间优化】

“玲珑杯”ACM比赛 Round #12 (D) 【矩阵快速幂的时间优化】

//首先,感谢Q巨

定义状态向量b[6]

  b[0]:三面临红色的蓝色三角形个数

  b[1]:两面临红色且一面临空的蓝色三角形个数

  b[2]:一面临红色且两面临空的蓝色三角形个数

  b[3]:三面临红色的黄色三角形个数

  b[4]:两面临红色且一面临绿+的黄色三角形个数

  b[5]:一面临红色且两面临绿+的黄色三角形个数

转移矩阵:

[3 1 0 0 0 0;
0 2 2 0 0 0;
0 1 3 0 0 0;
3 2 1 0 0 0;
0 0 0 6 3 0;
0 0 0 0 2 4]

最朴素的TLE代码

#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N=6;
const LL mod=1234321237;
LL b[N]= {0,0,6,0,0,0};        //此处初始化列向量 
LL hh[N][N]={{3,1,0,0,0,0},
             {0,2,2,0,0,0},
             {0,1,3,0,0,0},
             {3,2,1,0,0,0},
             {0,0,0,6,3,0},
             {0,0,0,0,2,4}
            };
struct Mat
{
    LL mat[N][N];
} A;
Mat Mut(Mat a,Mat b)
{
    Mat c;
    memset(c.mat,0,sizeof(c.mat));
    for(int k=0; k<N; k++)
        for(int i=0; i<N; i++)
            if(a.mat[i][k])
            for(int j=0; j<N; j++)
            {
                c.mat[i][j]+=a.mat[i][k]*b.mat[k][j]%mod;
                c.mat[i][j]=c.mat[i][j]%mod;
            }
    return c;
}
Mat Qpow(Mat a,LL n)
{
    Mat c;
    for(int i=0; i<N; ++i)
        for(int j=0; j<N; ++j)
            c.mat[i][j]=(i==j);
    for(; n; n>>=1)
    {
        if(n&1) c=Mut(c,a);
        a=Mut(a,a);
    }
    return c;
}
void cal(Mat A,LL n,LL b[],LL& Fn,LL& Gn)
{
    Mat A_=Qpow(A,n-1);
    Fn=Gn=0;
    LL c[N]={0};
    for(int i=0;i<N;i++)
        for(int j=0;j<N;j++)
            c[i]=(c[i]+A_.mat[i][j]*b[j])%mod;
    Fn=(c[0]+c[1]+c[2])%mod;
    Gn=(c[3]+c[4]+c[5])%mod;
}
void init_A()
{
    for(int i=0;i<N;i++)
        for(int j=0;j<N;j++)
            A.mat[i][j]=hh[i][j];
}

int main()
{
    LL n,Fn,Gn;
    init_A();
    while(cin>>n)
    {
        if(n==1)
        {
            puts("1 0");
            continue;
        }
        n--;
        cal(A,n,b,Fn,Gn);        //假定下标从第1项开始计数,求第n项 
        cout<<Fn<< <<Gn<<endl;
    }
}

貌似(只是貌似)被优化但仍然TLE的代码

#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N=6;
const LL mod=1234321237;
LL b[N]= {0,0,6,0,0,0};        //此处初始化列向量 
LL hh[N][N]={{3,1,0,0,0,0},
             {0,2,2,0,0,0},
             {0,1,3,0,0,0},
             {3,2,1,0,0,0},
             {0,0,0,6,3,0},
             {0,0,0,0,2,4}
            };
struct Mat
{
    LL mat[N][N];
} A,F[62];
void printM(Mat x)
{
    puts("=================================================================");
    for(int i=0;i<N;i++)
    {
        for(int j=0;j<N;j++)
            printf("%10lld",x.mat[i][j]);
        puts("");
    }
}
Mat Mut(Mat a,Mat b)
{
    Mat c;
    memset(c.mat,0,sizeof(c.mat));
    for(int k=0; k<N; k++)
        for(int i=0; i<N; i++)
            if(a.mat[i][k])
            for(int j=0; j<N; j++)
            {
                c.mat[i][j]+=a.mat[i][k]*b.mat[k][j]%mod;
                c.mat[i][j]=c.mat[i][j]%mod;
            }
    return c;
}

Mat Qpow(Mat a,LL n)
{
    Mat c;
    for(int i=0; i<N; ++i)
        for(int j=0; j<N; ++j)
            c.mat[i][j]=(i==j);
    for(; n; n>>=1)
    {
        if(n&1) c=Mut(c,a);
        a=Mut(a,a);
    }
    return c;
}
void cal(Mat A,LL n,LL b[],LL& Fn,LL& Gn)
{
    Mat A_;
    for(int i=0; i<N; ++i)
        for(int j=0; j<N; ++j)
            A_.mat[i][j]=(i==j);
    for(int i=0;i<60&&n;i++,n>>=1)
        if(n&1) A_=Mut(A_,F[i]);
    //printM(A_);
    Fn=Gn=0;
    LL c[N]={0};
    for(int i=0;i<N;i++)
        for(int j=0;j<N;j++)
            c[i]=(c[i]+A_.mat[i][j]*b[j])%mod;
    Fn=(c[0]+c[1]+c[2])%mod;
    Gn=(c[3]+c[4]+c[5])%mod;
}

void init_A()
{
    for(int i=0;i<N;i++)
        for(int j=0;j<N;j++)
            A.mat[i][j]=hh[i][j];
    F[0]=A;
    for(int i=1;i<61;i++)
        F[i]=Mut(F[i-1],F[i-1]);
}


int main()
{
    LL n,Fn,Gn;
    init_A();
    int T;
//    cin>>T;
    scanf("%lld",&T);
    while(T--)
    {
//        cin>>n;
        scanf("%lld",&n);
        if(n==1)
        {
            puts("1 0");
            continue;
        }
        n-=2;
        cal(A,n,b,Fn,Gn);        //假定下标从第1项开始计数,求第n项 
//        cout<<Fn<<‘ ‘<<Gn<<endl;
        printf("%lld %lld\n",Fn,Gn);
    }
}

矩阵相乘一次的复杂度是O(N^3)的,不过预处理2^i(i:0~60)的矩阵后,可以用向量记录中间结果,而矩阵*向量的复杂度为O(N^2)

最终复杂度: O(T * lb(n) * N^2)->O(1e5 * 60 * 36)->O(2e8)

最终可以AC的代码

#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N=6;
const LL mod=1234321237;
LL b[N]= {0,0,6,0,0,0};        //此处初始化列向量 
LL hh[N][N]={{3,1,0,0,0,0},
             {0,2,2,0,0,0},
             {0,1,3,0,0,0},
             {3,2,1,0,0,0},
             {0,0,0,6,3,0},
             {0,0,0,0,2,4}
            };
struct Mat
{
    LL mat[N][N];
} A,F[62];
void printM(Mat x)
{
    puts("=================================================================");
    for(int i=0;i<N;i++)
    {
        for(int j=0;j<N;j++)
            printf("%10lld",x.mat[i][j]);
        puts("");
    }
}
Mat Mut(Mat a,Mat b)
{
    Mat c;
    memset(c.mat,0,sizeof(c.mat));
    for(int k=0; k<N; k++)
        for(int i=0; i<N; i++)
            if(a.mat[i][k])
            for(int j=0; j<N; j++)
            {
                c.mat[i][j]+=a.mat[i][k]*b.mat[k][j]%mod;
                c.mat[i][j]=c.mat[i][j]%mod;
            }
    return c;
}

Mat Qpow(Mat a,LL n)
{
    Mat c;
    for(int i=0; i<N; ++i)
        for(int j=0; j<N; ++j)
            c.mat[i][j]=(i==j);
    for(; n; n>>=1)
    {
        if(n&1) c=Mut(c,a);
        a=Mut(a,a);
    }
    return c;
}
void cal(Mat A,LL n,LL b[],LL& Fn,LL& Gn)
{
    Mat A_;
    LL c[N]={0,0,6,0,0,0};
    for(int i=0; i<N; ++i)
        for(int j=0; j<N; ++j)
            A_.mat[i][j]=(i==j);
    for(int i=0;i<60&&n;i++,n>>=1)
        if(n&1)
        {
            LL tres[6]={0,0,0,0,0,0};
            for(int j=0;j<6;j++)    //矩阵的行 
                for(int k=0;k<6;k++)    //矩阵的列 
                    tres[j]=(tres[j]+F[i].mat[j][k]*c[k])%mod;
            for(int j=0;j<6;j++)
                c[j]=tres[j]%mod;
        }
    Fn=Gn=0;
    Fn=(c[0]+c[1]+c[2])%mod;
    Gn=(c[3]+c[4]+c[5])%mod;
}

void init_A()
{
    for(int i=0;i<N;i++)
        for(int j=0;j<N;j++)
            A.mat[i][j]=hh[i][j];
    F[0]=A;
    for(int i=1;i<61;i++)
        F[i]=Mut(F[i-1],F[i-1]);
}


int main()
{
    LL n,Fn,Gn;
    init_A();
    int T;
//    cin>>T;
    scanf("%lld",&T);
    while(T--)
    {
//        cin>>n;
        scanf("%lld",&n);
        if(n==1)
        {
            puts("1 0");
            continue;
        }
        n-=2;
        cal(A,n,b,Fn,Gn);        //假定下标从第1项开始计数,求第n项 
//        cout<<Fn<<‘ ‘<<Gn<<endl;
        printf("%lld %lld\n",Fn,Gn);
    }
}

 

“玲珑杯”ACM比赛 Round #12 (D) 【矩阵快速幂的时间优化】