矩阵快速幂,动态规划(cjj's string game,HDU 5863)

本文探讨了如何使用矩阵快速幂方法解决大规模计数问题,特别是针对字符串匹配问题,通过动态规划定义状态,并利用矩阵快速幂进行高效计算。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一开始考虑后缀数组。

但有两个原因感觉不行。

1、没有一个给定的串让我来处理成后缀数组。

2、发现n很大,1e9,但m很小才10。时空完全无法承受。


然后考虑计算出来。

很容易想到让第一个串随便组合,然后尝试让第二个串满足它。

对于任意一个串,要求另一个串满足它的方案数都是一样多的。

满足的条件只关乎每个下标上的字符是否相等。显然和具体字符无关,只和字符的种数有关,所以方案数一样多。


一开始尝试组合数学。

关于n个数选m个数互不相邻的讲解,包括直线和圈上的:

https://www.zhihu.com/question/51429770/answer/125828470

当m=1的时候,理论上还可以算。用二项式定理可以O(logn)计算。

但是当m更大的时候,我们需要计算,n个数选a段不相邻的数,段长最多为b的方案数,理论上可以动态规划。

但是a是O(n)级别的,状态定义和转移时空都会爆。而且还要动规O(n)次。感觉不是很靠谱。


后来考虑计数DP

只需要计算一个串满足另一个固定串的方案数有多少。

满足就是说连续相等的串的长度至多为m,且至少有1个。

m才10,很快定义了一个状态并找到了转移。

dp[i][j][k]表示长度为i的串,最后j个字符相等,k表示是否至少有1个长度为m的串。

转移也是很简单,就是直接各种情况转移一下就好了。

但问题是n很大,有O(1e9)。可以考虑用矩阵快速幂来DP。

很多线性的DP都可以用矩阵快速幂来计算,最经典的就是斐波那契数列。

本题也是一样,我们直接把dp[0]的状态放到一个1行的矩阵里面,然后构造转移矩阵A,然后矩阵快速幂,A^n乘以这个初始矩阵就是dp[n]的状态。然后从dp{n]中取出你想要的值就好了。

当然我们没有必要真的构造出那个1行的矩阵。自己假想它和A^n相乘,然后脑补,直接从A^n这个矩阵中取走你想要的值就好了。


看了网上别人的解法,DP出最多为m的所有方案数。

然后DP[m]-DP[m-1]就是答案。

这个矩阵简单点,速度会快。


代码

#include<stdio.h>
using namespace std;
typedef long long ll;
const int mod = 1000000007;

struct Mat
{
    int N;
    int A[25][25];
    void mem()
    {
        for(int i=0;i<N;i++)
            for(int j=0;j<N;j++)
                A[i][j]=0;
    }
    void I()
    {
        for(int i=0;i<N;i++)
            A[i][i]=1;
    }
    void init(int m,int k)
    {
        N=2*(m+1);
        mem();
        for(int i=0;i<m;i++) A[i][0]=k-1;
        for(int i=0;i<N-1;i++)
        {
            int j=i+1;
            if(i==m) continue;
            if(j==m) continue;
            A[i][j]=1;
        }
        for(int i=m+1;i<N;i++)
            A[i][m+1]=k-1;
        A[m-1][N-1]=1;
    }
    Mat operator * (const Mat& rhs) const
    {
        Mat ret;
        ret.N=N;
        ret.mem();
        for(int i=0;i<N;i++)
            for(int j=0;j<N;j++)
                for(int k=0;k<N;k++)
                    ret.A[i][j]=(ret.A[i][j]+1ll*A[i][k]*rhs.A[k][j]%mod)%mod;
        return ret;
    }
    Mat operator ^ (int n)
    {
        Mat ret;
        ret.N=N;
        ret.mem();
        ret.I();
        Mat x;
        x=*this;
        while(n)
        {
            if(n&1) ret=ret*x;
            x=x*x;
            n>>=1;
        }
        return ret;
    }
    int getans()
    {
        int ret=0;
        for(int i=N/2;i<N;i++)
            ret=(1ll*ret+A[0][i])%mod;
        return ret;
    }
}MAT;

int mp(int x,int n)
{
    int ret=1;
    while(n)
    {
        if(n&1) ret=1ll*ret*x%mod;
        x=1ll*x*x%mod;
        n>>=1;
    }
    return ret;
}

int n,m,k;

void solve()
{
    scanf("%d %d %d",&n,&m,&k);
    MAT.init(m,k);
    MAT=MAT^n;
    ll ans=1ll*mp(k,n)*MAT.getans()%mod;
    printf("%lld\n",ans);
}

int main()
{
    int T;
    scanf("%d",&T);
    while(T--) solve();
    return 0;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值