Luogu P10581 蓝桥杯2024国A 重复的串 题解 [ 蓝 ] [ KMP ] [ 动态规划 ] [ 矩阵加速 ]

重复的串:KMP + dp 的板子题。

暴力 dp

设计 dpk,i,j 表示主串匹配到第 i 位,模式串有 j 位已匹配完成,目前已完成 k 次匹配的方案数。

不难写出暴力的填表法转移式子,发现是平方级别的转移,所以使用填表法不行,我们尝试刷表法。

dpk,i,j 要转移去的地方显然是第 i+1 位,于是我们要观察 j 里哪些是合法的,很容易就想到用 KMP 加速这个过程,因为 KMP 做的也是匹配到某一位后,求出下一位的最大匹配长度。

假设 m 为模式串的长度。

对于 dpk,i,j 我们枚举第 i+1 位的字符是什么,依次和当前需要匹配的字符进行比较,然后用 KMP 的 next 数组求出下一位的最大匹配长度 l

  • 当最大匹配长度为 m 时,这时候 k 就要 +1,所以 dpk+1,i+1,nextldpk+1,i+1,nextl+dpk,i,j
  • 当最大匹配长度小于 m 时,直接转移即可,dpk,i+1,ldpk,i+1,l+dpk,i,j

时间复杂度是 O(k||n) 的,显然不可过。

矩阵优化

发现当值域非常大的 i 确定时,剩下两维的数量很小,只有 90 个值左右,考虑矩阵优化 dp。

我们把 i 这一位提取到前面来,作为快速幂的幂次即可。

构造矩阵有点难描述,看代码吧,大体就是做一遍上面的那个转移,注意细节问题就好了。

另外,当 j=m 时,不能进行转移。

时间复杂度 O((k||)3logn)

关于完整匹配

本题中,完整匹配后可以跳到 next 处,是因为这样统计方案不会重复。而其他题中,有时候完整匹配是不能直接跳回 next 的,因为完全匹配后和 next 的情况可能是不同的。这时候就要对 next 进行转移,其实就不用特判 next 就行了,下次它会自己跳回去自己的 next 的。

代码

#include <bits/stdc++.h>
#define fi first
#define se second
#define lc (p<<1)
#define rc ((p<<1)|1)
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int,int> pi;
const ll mod=998244353;
int n,ne[35],m;
char c[35];
struct mat{
    ll a[105][105];
    mat(){memset(a,0,sizeof(a));}
    mat operator*(const mat &t)const{
        mat res;
        for(int i=0;i<=100;i++)
        {
            for(int k=0;k<=100;k++)
            {
                for(int j=0;j<=100;j++)
                {
                    res.a[i][j]=(res.a[i][j]+a[i][k]*t.a[k][j])%mod;
                }
            }
        }
        return res;
    }
};
void construct_dp(mat &dp)
{
    for(int lv=0;lv<=2;lv++)
    {
        int ns=1+lv*(m+1);
        for(int i=ns;i<ns+m;i++)
        {
            for(int j=0;j<26;j++)
            {
                char nc=('a'+j);
                int now=i-ns;
                while(now&&nc!=c[now+1])now=ne[now];
                if(nc==c[now+1])now++;
                if(now==m)
                {
                    if(lv==2)continue;
                    now=ne[now];
                    dp.a[i][ns+m+1+now]=(dp.a[i][ns+m+1+now]+1)%mod;
                }
                else dp.a[i][ns+now]=(dp.a[i][ns+now]+1)%mod;
            }
        }    
    }
}
void construct_init(mat &s){s.a[1][1]=1;}
mat qpow(mat a,ll b)
{
    mat res;
    for(int i=0;i<=100;i++)res.a[i][i]=1;
    while(b)
    {
        if(b&1)res=res*a;
        a=a*a;
        b>>=1;
    }
    return res;
}
int main()
{
    ios::sync_with_stdio(0);
    cin.tie(0);
    cout.tie(0);
    cin>>c+1>>n;
    m=strlen(c+1);
    int now=0;
    for(int i=2;i<=m;i++)
    {
        while(now&&c[now+1]!=c[i])now=ne[now];
        if(c[now+1]==c[i])now++;
        ne[i]=now;
    }
    mat dp;
    construct_dp(dp);
    mat s;
    construct_init(s);
    dp=s*qpow(dp,n);
    ll ans=0;
    for(int i=1+2*(m+1);i<1+3*(m+1);i++)ans=(ans+dp.a[1][i])%mod;
    cout<<ans;
    return 0;
}
posted @   KS_Fszha  阅读(6)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 微软正式发布.NET 10 Preview 1:开启下一代开发框架新篇章
· 没有源码,如何修改代码逻辑?
· PowerShell开发游戏 · 打蜜蜂
· 在鹅厂做java开发是什么体验
· WPF到Web的无缝过渡:英雄联盟客户端的OpenSilver迁移实战
点击右上角即可分享
微信分享提示