bzoj4037: [HAOI2015]数字串拆分(动态规划+矩阵)

www.cnblogs.com/shaokele/


bzoj4037: [HAOI2015]数字串拆分(动态规划+矩阵)

  Time Limit: 10 Sec
  Memory Limit: 256 MB

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

  仅输出一个数表示答案
  

Sample Input 1

  123
  3
 

Sample Output 1

  394608467
  

HINT

   对于100%的数据,字符串长度不超过500,m<=5
  

题目地址:  bzoj4037: [HAOI2015]数字串拆分
题目大意:

  定义 \(f(x)\) 为把 \(x\) 有序拆分成若干 \(1\)\(m\) 之间数的方案数。
  例如当 m=2 时,4=1+1+1+1=1+1+2=1+2+1=2+1+1=2+2,因此 \(f(x)=5\)
  给定一个 n 位数字串 \(S\) 将这个数字串分割成若干数字(允许前导零),将它们加起来,求 \(f\) ,再对所有方案求和并输出。
  例如 123 的答案 g(123)=f(1+2+3)+f(12+3)+f(1+23)+f(123)。

题解:

  \[ f(x)=\sum_{i=1}^{m} f(x-i)\]
  发现 m 非常小,可以用矩阵乘法快速求出 f(x)
  对于最终答案,我们考虑 dp ,g(i) 表示前 i 位的答案,由于 f 中还有加法,我们不能转移。
  假设矩阵为 A ,f(x) 和 \(A^x\) 是等价的。 \(f(a+b)=A^{a+b}=A^aA^b\) ,也就是说我们可以化加为乘
  在求 g 时,保留矩阵形式,那么就有转移:
  \[ g(i)=\sum_{j=1}^{i-1}g(j)*A^{S[j+1,i]}。\]
  


AC代码

#include <cstdio>
#include <cstring>
#define ll long long
using namespace std;
const int N=1005,Mod=998244353;
int n,m,ch[N];
char st[N];
struct matrix{
    int A[6][6];
}f[15][N],g[N],tran;
matrix operator *(matrix a,matrix b){
    matrix res;
    memset(res.A,0,sizeof(res.A));
    for(int i=1;i<=m;i++)
        for(int k=1;k<=m;k++)
            if(a.A[i][k])
                for(int j=1;j<=m;j++){
                    res.A[i][j]=(res.A[i][j]+(ll)a.A[i][k]*b.A[k][j]%Mod);
                    if(res.A[i][j]>=Mod)res.A[i][j]-=Mod;
                }
    return res;
}
matrix operator +(matrix a,matrix b){
    for(int i=1;i<=m;i++)
        for(int j=1;j<=m;j++){
            a.A[i][j]+=b.A[i][j];
            if(a.A[i][j]>=Mod)a.A[i][j]-=Mod;
        }
    return a;
}
matrix fast_pow(matrix x,int e){
    matrix res=x;
    while(e){
        if(e&1)res=res*x;
        x=x*x;
        e>>=1;
    }
    return res;
}
int main(){
    scanf("%s",st+1);
    n=strlen(st+1);
    scanf("%d",&m);
    for(int i=1;i<=n;i++)ch[i]=st[i]-'0';
    for(int i=1;i<=m;i++){
        tran.A[i][m]=f[0][1].A[i][i]=1;
        if(i>1)tran.A[i][i-1]=1;
    }
    for(int i=1;i<=9;i++){
        f[i][1]=f[i-1][1]*tran;
        for(int j=2;j<=n;j++)
            f[i][j]=fast_pow(f[i][j-1],9);
    }
    g[0].A[1][m]=1;matrix tmp;
    for(int i=1;i<=n;i++){
        tmp=f[ch[i]][1];
        for(int j=i-1;j>=0;j--){
            g[i]=g[i]+g[j]*tmp;
            if (j && ch[j])tmp=tmp*f[ch[j]][i-j+1];
        }
    }
    printf("%d\n",g[n].A[1][m]);
    return 0;
}

转载于:https://www.cnblogs.com/shaokele/p/9310309.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值