bzoj 4037: [HAOI2015]数字串拆分【dp+矩阵加速】

首先f长得就很像能矩阵优化的,先构造转移矩阵(这里有一点神奇的地方,我看网上的blog和我构造的矩阵完全不一样还以为我的构造能力又丧失了,后来惊奇的发现我把那篇blog里的构造矩阵部分换成我的构造方式,交了一下完全没问题2333,并不知道为啥)
好久没写矩阵加速了,顺便说一下我的构造方法吧:
首先明确转移矩阵的目的,设m为构成f[i]的最小项f[i-m],也就是f[i]=f[i-m]+f[i-...]+f[i-...]+....,其中i-m是最小的。我们需要构造一个m大小的矩阵,使得{f[i-m],f[i-m+1].....f[i-1}乘上这个正方形矩阵变成{f[i-m+1],f[i-m+2]....f[i]}然后因为矩阵乘法是一行乘一列,所以每个右边的每个f[i]都对应了一行矩阵和左边的每项依次相乘。拿这道题的递推式,m=3为例:
1242898-20180320100355428-1946558175.png
以上,我也不知道我在说什么。
然后这道题的精髓在于它使用f的矩阵进行g的dp。设f[i]为前i位的答案,因为矩阵乘法的结合律,我们可以记\( g[i]=\sum_{j=1}^{m}g[j]*c[j+1][i] \)。然后考虑如何预处理出c,可以设c[i][j]为i这个数(1<=i<=9)的\( j^{10} \)次(假装高精)。
总之,一道好题。

#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
const int N=505,mod=998244353;
int n,m;
char s[N];
struct qwe
{
    int a[7][7];
    void init()
    {
        memset(a,0,sizeof(a));
    }
    void pre()
    {
        memset(a,0,sizeof(a));
        for(int i=1;i<=m;i++)
            a[i][i]=1;
    }
    qwe operator * (const qwe &b) const
    {
        qwe c;
        c.init();
        for(int k=1;k<=m;k++)
            for(int i=1;i<=m;i++)
                for(int j=1;j<=m;j++)
                    c.a[i][j]=(c.a[i][j]+1ll*a[i][k]*b.a[k][j]%mod)%mod;
        return c;
    }
    qwe operator + (const qwe &b) const
    {
        qwe c;
        c.init();
        for(int i=1;i<=m;i++)
            for(int j=1;j<=m;j++)
                c.a[i][j]=(b.a[i][j]+a[i][j])%mod;
        return c;
    }
}f[N],a,c[15][N];
qwe ksm(qwe a,int b)
{
    qwe r;
    r.pre();
    while(b)
    {
        if(b&1)
            r=r*a;
        a=a*a;
        b>>=1;
    }
    return r;
}
int main()
{
    scanf("%s%d",s+1,&m);
    n=strlen(s+1);
    for(int i=1;i<=n;i++)
        s[i]-='0';
    for(int i=1;i<m;i++)
        a.a[i][i+1]=1;
    for(int i=1;i<=m;i++)
        a.a[m][i]=1;
    c[0][1].pre();
    for(int i=1;i<=9;i++)
    {
        c[i][1]=c[i-1][1]*a;
        for(int j=2;j<=n;j++)
            c[i][j]=ksm(c[i][j-1],10);
    }
    f[0].a[1][m]=1;
    for(int i=1;i<=n;i++)
    {
        qwe tmp=c[s[i]][1];
        for(int j=i-1;j>=0;j--)
        {
            f[i]=f[i]+f[j]*tmp;
            if(j&&s[j])
                tmp=tmp*c[s[j]][i-j+1];
        }
    }
    printf("%d\n",f[n].a[1][m]);
    return 0;
}

转载于:https://www.cnblogs.com/lokiii/p/8607611.html

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值