【HAOI2015】bzoj4037 数字串拆分

229 篇文章 0 订阅
175 篇文章 0 订阅

Description
你有一个长度为n的数字串。定义f(S)为将S拆分成若干个1~m的数的和的方案数,比如m=2时,f(4)=5,分别为4=1+
1+1+1你可以将这个数字串分割成若干个数字(允许前导0),将他们加起来,求f,并求和。比如g(123)=f(1+2+3)
+f(1+23)+f(12+3)+f(123)。已知字符串和m后求答案对998244353(7×17×223+1,一个质数)取模后的值。 Input

第一行输入一个字符串,第二行输入m Output

仅输出一个数表示答案

构造 m×m 的转移矩阵 A 满足ai,j=1当且仅当 j=1 i+1=j 。可以发现, fn=An1,1
接下来考虑在字符串上dp,但是发现如果直接做不满足乘法分配律,也就是不能写成 dpi=i1j=0dpjfj+1..i 。但是因为 f 可以写成矩阵的幂的形式,这样就可以提取公因式,满足了分配律。
在算一段区间的时候,可以预处理出来x10y的答案,然后直接相乘,注意不要写成 O(n3)

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
#define LL long long
const int mod=998244353;
int m,n;
char s[510];
struct mat
{
    int a[6][6];
    void init()
    {
        for (int i=1;i<=m;i++)
            for (int j=1;j<=m;j++)
                a[i][j]=(j==1||i+1==j);
    }
    void I()
    {
        for (int i=1;i<=m;i++)
            for (int j=1;j<=m;j++)
                a[i][j]=(i==j);
    }
    mat operator + (const mat &mm) const
    {
        mat ret;
        for (int i=1;i<=m;i++)
            for (int j=1;j<=m;j++)
                ret.a[i][j]=(a[i][j]+mm.a[i][j])%mod;
        return ret;
    }
    mat operator * (const mat &mm) const
    {
        mat ret;
        for (int i=1;i<=m;i++)
            for (int j=1;j<=m;j++)
            {
                ret.a[i][j]=0;
                for (int k=1;k<=m;k++)
                    ret.a[i][j]=(ret.a[i][j]+(LL)a[i][k]*mm.a[k][j]%mod)%mod;
            }
        return ret;
    }
    mat pow(int x)
    {
        mat ret,base;
        ret.I();
        for (int i=1;i<=m;i++)
            for (int j=1;j<=m;j++)
                base.a[i][j]=a[i][j];
        while (x)
        {
            if (x&1) ret=ret*base;
            base=base*base;
            x>>=1;
        }
        return ret;
    }
}f[10][510],dp[510],t;
mat cal(int l,int r)
{
    mat ret;
    ret.I();
    for (int i=l;i<=r;i++)
        ret=ret*f[s[i]-'0'][r-i];
    return ret;
}
int main()
{
    scanf("%s",s+1);
    n=strlen(s+1);
    scanf("%d",&m);
    for (int i=0;i<=n;i++) f[0][i].I();
    f[1][0].init();
    for (int i=1;i<=n;i++) f[1][i]=f[1][i-1].pow(10);
    for (int i=2;i<=9;i++)
        for (int j=0;j<=n;j++)
            f[i][j]=f[i-1][j]*f[1][j];
    dp[0].I();
    for (int i=1;i<=n;i++)
    {
        t=f[s[i]-'0'][0];
        for (int j=i-1;j>=0;j--)
        {
            dp[i]=dp[i]+dp[j]*t;
            if (j) t=t*f[s[j]-'0'][i-j];
        }
    }
    printf("%d\n",dp[n].a[1][1]);
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值