矩阵快速幂好题分享

poj3420 Quad Tiling
这些方案数的题,一般是先找到某些递推式,然后,根据递推式做之后的优化工作。
首先这是个铺地板的经典问题,对于这类方案数的求解有一个比较重要的思想,就是考虑从小往上的最小的可分割矩阵来分开求方案数。我们首先设\(a_i\)表示i4的方格不可分割的最小矩阵的发案数。这个需要自己画图发现,a1=1,a2=4,a3=2,a4=3...之后发现奇数次为2,偶数次为3.之后考虑递推式n,设f表示它所求的值,那么考虑按最小分割的矩阵分类讨论,f[n]=a1f[n-1]+a2f[n-2]+...+an-1f[1]=f[n-1]+4f[n-2]+2(f[n-3]+f[n-5]...)+3(f[n-4]+f[n-6]+...)
对于这类的递推式子,如果我们要求通项的话,一般的做法就是将f[n-1]写出,然后相加减即可。(有点错位相减的意思。)
那么f[n-1]=f[n-2]+4
f[n-3]+2(f[n-4]+f[n-6]+...)+3(f[n-5]+f[n-7]+...)
两者做和,得出f[n]=5f[n-2]+6f[n-3]+5(f[n-4]+f[n-5]+...)
则我们继续,f[n-1]=5f[n-3]+6f[n-4]+5(f[n-5]+f[n-6]+...)
两式相减:f[n]-f[n-1]=5f[n-2]+f[n-3]-f[n-4].则f[n]=f[n-1]+5f[n-2]+f[n-3]-f[n-4].
之后直接用矩阵快速幂递推即可。

#include <cstdio>
#include <iostream>
#include <algorithm>
#include <stdio.h>
#include <string.h>
#define ll long long
using namespace std;
const int N=5;
int n,m;
struct wy
{
    ll a[N][N];
    wy() {memset(a,0,sizeof(a));}
    inline void clear() {memset(a,0,sizeof(a));}
    wy friend operator*(wy a,wy b)
    {
        wy c;
        for(int i=1;i<N;++i)
            for(int j=1;j<N;++j)
                for(int k=1;k<N;++k) c.a[i][j]=((c.a[i][j]+a.a[i][k]*b.a[k][j])%m+m)%m;
        return c;         
    } 
    wy friend operator^(wy a,ll y)
    {
        wy c;
        for(int i=1;i<N;++i) c.a[i][i]=1;
        while(y)
        {
            if(y&1) c=c*a;
            y>>=1;
            a=a*a;
        }
        return c;
    }
};
int main()
{
//    freopen("1.in","r",stdin);
    while(scanf("%d%d",&n,&m))
    {
        if(n==0&&m==0) break;
        wy A,B,C;
        A.a[1][1]=1;A.a[1][2]=5;
        A.a[1][3]=11;A.a[1][4]=36;
        if(n<=4) printf("%lld\n",A.a[1][n]%m);
        else
        {
            B.a[2][1]=1;B.a[3][2]=1;
            B.a[4][3]=1;B.a[1][4]=-1;
            B.a[2][4]=1;B.a[3][4]=5;
            B.a[4][4]=1;
            B=B^(n-4);
            C=A*B;
            printf("%lld\n",C.a[1][4]%m);
        }
    }
    return 0;
}

#2476. 战场的数目
真的是神仙题目...
首先可以发现战场的周长一定是偶数,这个可以考虑当初没合并之前每个小正方形的周长都是6,合并的时候两个重合的面导致两个面被不计,所以周长一定是偶数。既然如此,我们可以考虑设f[i]表示周长为2i的方案数。接下来的就是讨论方案数中最难的一部分,怎么划分方案,可以用上之前的信息。首先我们可以想到左右两边的1似乎有某些性质,考虑若最左边或最右边有一个1,我们把这个1去掉,减少的周长为2,即f[i-1]。那么这个方案数就是2f[i-1]。但这样会有重复,考虑两边都有1的情况,我们考虑将这两边的1都去掉,即f[i-2]。接下来考虑两边都没1的情况,我们可以将最下面一层去掉。这样仍是我们之前的状态。最后f[i]=3*f[i-1]-f[i-2],最后别忘了减去所有矩阵的方案,即n-1.直接用矩阵加速即可。

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int P=987654321;
struct wy
{
    ll a[3][3];
    wy() {memset(a,0,sizeof(a));}
    void clear(){memset(a,0,sizeof(a));}
    wy friend operator*(wy a,wy b)
    {
        wy c;
        for(int i=1;i<=2;++i)
            for(int j=1;j<=2;++j)
                for(int k=1;k<=2;++k) c.a[i][j]=((c.a[i][j]+a.a[i][k]*b.a[k][j])%P+P)%P;
        return c;
    }
    wy friend operator^(wy x,ll y)
    {
        wy ans;
        for(int i=1;i<=2;++i) ans.a[i][i]=1;
        while(y)
        {
            if(y&1) ans=ans*x;
            y>>=1;
            x=x*x;    
        }    
        return ans;
    } 
}A,B,C;
int main()
{
//    freopen("1.in","r",stdin);
    int x;
    while(scanf("%d",&x))
    {
        if(!x) break;
        if(x%2==1||x<8) printf("%d\n",0);
        else
        {
            if(x==8) {printf("%d\n",2);continue;}
            else if(x==10) {printf("%d\n",9);continue;}
            int d=x/2;
            A.a[1][1]=5;A.a[1][2]=13;
            B.a[1][1]=0;B.a[1][2]=-1;
            B.a[2][1]=1;B.a[2][2]=3;
            B=B^(d-5);
            C=A*B;
            printf("%lld\n",((C.a[1][2]-(d-1))%P+P)%P);
        }
    }
    return 0;
}
posted @ 2022-01-18 15:42  逆天峰  阅读(48)  评论(0编辑  收藏  举报
作者:逆天峰
出处:https://www.cnblogs.com/gcfer//