qwq真的是一道好题qwq自己做基本是必不可能做出来的。

首先,如果这个题目只是求一个\(f\)数组的话,那就是一道裸题。

首先,根据样例 根据题目描述,我们能发现其实同样数字的不同排列,也是属于不同的方案的,那统计起来其实方便很多。

首先我们发现,对于\(i\)这个数,他可以拆出来\([1,m]\)任何一个数,接在对应的\(f[i-1]到f[i-m]\)

也就是说\(f[i]=f[i-1]+f[i-2]+f[i-3]....f[i-m]\)

qwq那我们可以构造出转移矩阵

以\(m=3\)为栗子

0 0 1
1 0 1
0 1 1

我们假设这个转移矩阵是\(a\)

那我们就可以直接将每一个\(f[i]\)转化成,初始矩阵\(\times a^i\)的形式。

qwq但这个离我们求出来\(g\)数组还差好远。

由于\(g\)数组涉及\(f\)数组的拆分形式。

那我们不妨观察一下\(f[i+j]\)等于多少。

\[f[i+j]=a^{i+j} = a^i \times a^j = f[i]*f[j]
\]

那么我们就可以直接用矩阵乘法的形式来表示拼接了。

那g数组的转移式子,也就比较好求了

\[g[i]=\sum_{j=0}^{i-1}g[j]*d[j+1][i]
\]

其中\(d[j+1][i]\)表示\([j+1,i]\)这些数从左到右排起来,组成的数的\(f\)的对应矩阵是多少。

这里转移式子的意义是,我们对于当前位,考虑枚举所有他的后缀,和前面任意的\(f[i]\)的值乘起来,都是一个合法的方案(原理根据上面对\(f[i+j]\)的讨论)。

之所以能直接用\(g\)数组来乘转移矩阵而不是一个一个分别乘。

是因为同大小的矩阵具有乘法分配律!

所以\(g\)也就可以直接和对应矩阵乘起来了

那么最后的\(ans\),就应该是初始矩阵

还是以\(m=3\)为例

0 0 1
0 0 0
0 0 0

乘上\(g[n]\)之后第一行最后一个元素的值,也就相当于\(g[n]\)的第m行m列的那个元素。

现在整个问题的瓶颈到了怎么求\(d\)数组,由于数值太大,所以我们没有办法直接快速乘。

qwq

现在考虑递推

我们令\(b[i][j]\)表示\(i\times 10^j\)的对应的f的转移矩阵是多少。

比较容易发现这个数组还是很好递推的。

每次\(b[i][j]=qsm(b[i][j-1],10)\)

那知道这个数组,其实\(d\)数组也就不难推了

我们首先令\(d[i][i]=b[s[i]-'0'][0]\)

那么\(d[j][i]=d[j+1][i]*b[s[j]-'0'][i-j]\)

那么到这里这个问题也就基本解决了。

感觉细节真的是很多。

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
#include<queue>
#include<map>
#include<set>
#define mk make_pair
#define ll long long
#define int long long
using namespace std;
inline int read()
{
int x=0,f=1;char ch=getchar();
while (!isdigit(ch)) {if (ch=='-') f=-1;ch=getchar();}
while (isdigit(ch)) {x=(x<<1)+(x<<3)+ch-'0';ch=getchar();}
return x*f;
}
const int maxn = 7;
const int mod = 998244353;
struct Ju{
int x,y;
int a[maxn][maxn];
Ju operator * (Ju b)
{
Ju ans;
memset(ans.a,0,sizeof(ans.a));
ans.x=x;
ans.y=b.y;
for (int i=1;i<=ans.x;i++)
for (int j=1;j<=ans.y;j++)
for (int k=1;k<=y;k++)
ans.a[i][j]=(ans.a[i][j]+a[i][k]*b.a[k][j]%mod)%mod;
return ans;
}
Ju operator +(Ju b)
{
Ju ans;
memcpy(ans.a,a,sizeof(ans.a));
ans.x=x;
ans.y=y;
for (int i=1;i<=x;i++)
for (int j=1;j<=y;j++)
ans.a[i][j]=(ans.a[i][j]+b.a[i][j])%mod;
return ans;
}
};
Ju qsm(Ju i,int j)
{
Ju ans;
memset(ans.a,0,sizeof(ans.a));
ans.x=i.x;
ans.y=i.y;
for (int p=1;p<=i.x;p++) ans.a[p][p]=1;
while (j)
{
if (j&1) ans=ans*i;
i=i*i;
j>>=1;
}
return ans;
}
char s[2020];
int n,m;
Ju a[510][510]; //a[i][j]表示,区间[i,j]的数的矩阵是多少
Ju ymh[510][510]; //ymh[i][j]表示,i*10^j的矩阵是多少
Ju lyf;
Ju g[510];//大小相同的矩阵乘法具有分配律
void init() //初始化矩阵的行和列
{
for (int i=0;i<=9;i++)
for (int j=0;j<=n;j++)
{
ymh[i][j].x=ymh[i][j].y=m;
}
for (int i=0;i<=n;i++)
for (int j=0;j<=n;j++)
{
a[i][j].x=a[i][j].y=m;
}
for (int i=0;i<=n;i++) g[i].x=g[i].y=m;
}
void print(Ju a)
{
cout<<"*******"<<endl;
cout<<a.x<<" "<<a.y<<endl;
for (int i=1;i<=a.x;i++)
{
for (int j=1;j<=a.y;j++)
cout<<a.a[i][j]<<" ";
cout<<endl;
}
}
signed main()
{
init();
scanf("%s",s+1);
m=read();
n=strlen(s+1);
init();
ymh[0][0].x=m;
ymh[0][0].y=m;
for (int i=1;i<=m;i++) ymh[0][0].a[i][i]=1; //设置初始矩阵
lyf.x=m;
lyf.y=m;
for (int i=1;i<m;i++) lyf.a[i+1][i]=1;
for (int i=1;i<=m;i++) lyf.a[i][m]=1; //设置转移矩阵 for (int i=1;i<=9;i++)
ymh[i][0]=ymh[i-1][0]*lyf; //先递推出来所有i*10^0的答案
for (int i=0;i<=9;i++)
for (int j=1;j<=n;j++)
ymh[i][j]=qsm(ymh[i][j-1],10); //预处理出来所有的值
// print(ymh[1][0]);
// print(ymh[0][0]);
for (int i=n;i>=1;i--)
{
a[i][i]=ymh[s[i]-'0'][0];
for (int j=i-1;j>=1;j--)
{
a[j][i]=ymh[s[j]-'0'][i-j]*a[j+1][i];
}
} //预处理a数组
//print(a[1][1]);
g[0]=ymh[0][0];
for (int i=1;i<=n;i++)
{
for (int j=i-1;j>=0;j--)
g[i]=g[i]+(g[j]*a[j+1][i]); //计算g,因为g是个sigma的数组,而且同大小矩阵乘法具有分配律,所以这句话表示,当前[j+1,i]这个串可以和之前任意一种组合,组合成一个串。
}
int ans=0;
//print(g[n]);
//for (int i=1;i<=m;i++) ans=(ans+g[n].a[1][i])%mod;
//cout<<ans<<endl;
cout<<g[n].a[m][m]<<endl;
return 0;
}

洛谷3176 [HAOI2015]数字串拆分 (矩阵乘法+dp)的更多相关文章

  1. loj#2128. 「HAOI2015」数字串拆分 矩阵乘法

    目录 题目链接 题解 代码 题目链接 loj#2128. 「HAOI2015」数字串拆分 题解 \(f(s)\)对于\(f(i) = \sum_{j = i - m}^{i - 1}f(j)\) 这个 ...

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

    首先f长得就很像能矩阵优化的,先构造转移矩阵(这里有一点神奇的地方,我看网上的blog和我构造的矩阵完全不一样还以为我的构造能力又丧失了,后来惊奇的发现我把那篇blog里的构造矩阵部分换成我的构造方式 ...

  3. [HAOI2015]数字串拆分

    题目描述 你有一个长度为n的数字串.定义f(S)为将S拆分成若干个1~m的数的和的方案数,比如m=2时,f(4)=5,分别为4=1+1+1+1你可以将这个数字串分割成若干个数字(允许前导0),将他们加 ...

  4. BZOJ4037:[HAOI2015]数字串拆分——题解

    https://www.lydsy.com/JudgeOnline/problem.php?id=4037 你有一个长度为n的数字串.定义f(S)为将S拆分成若干个1~m的数的和的方案数,比如m=2时 ...

  5. bzoj4037 [HAOI2015]数字串拆分

    Description 你有一个长度为n的数字串.定义f(S)为将S拆分成若干个1~m的数的和的方案数,比如m=2时,f(4)=5,分别为4=1+1+1+1你可以将这个数字串分割成若干个数字(允许前导 ...

  6. BZOJ 4037 [HAOI2015]数字串拆分 ——动态规划

    拆分的情况下,发现f数组本身并不是很好递推. 因为f(123)=f(123)/f(12+3)/f(1+2+3). 然后考虑f可以怎么表示f(n)=a0*M^n M为转移矩阵. 然后发现 f(x+y)= ...

  7. 【LOJ】#2128. 「HAOI2015」数字串拆分

    题解 题中给的函数可以用矩阵快速幂递推 我们记一个数组dp[i](这个数组每个元素是一个矩阵)表示从1到i所有的数字经过拆分矩阵递推的加和 转移方法是 \(dp[i] = \sum_{j = 0}^{ ...

  8. 「区间DP」「洛谷P1043」数字游戏

    「洛谷P1043」数字游戏 日后再写 代码 /*#!/bin/sh dir=$GEDIT_CURRENT_DOCUMENT_DIR name=$GEDIT_CURRENT_DOCUMENT_NAME ...

  9. 洛谷 P3177 [HAOI2015]树上染色 树形DP

    洛谷 P3177 [HAOI2015]树上染色 树形DP 题目描述 有一棵点数为 \(n\) 的树,树边有边权.给你一个在 \(0 \sim n\)之内的正整数 \(k\) ,你要在这棵树中选择 \( ...

随机推荐

  1. call bind apply的区别

    call() 和apply()的第一个参数相同,就是指定的对象.这个对象就是该函数的执行上下文. call()和apply()的区别就在于,两者之间的参数. call()在第一个参数之后的 后续所有参 ...

  2. mybatis第一个程序随笔

    今天继续学习了解如何写一个mybatis程序 创建了Dao层 1.1 创建一个UserDao接口 1.2 创建UserMapper.xml文件 在mybaits中文手册查找配置信息 <?xml ...

  3. blender skin modifier 太好玩了

    https://docs.blender.org/manual/en/latest/modeling/modifiers/generate/skin.html 只需要像画火柴人一样把点连起来,skin ...

  4. 利用Struts2拦截器完成文件上传功能

    Struts2的图片上传以及页面展示图片 在上次的CRUD基础上加上图片上传功能 (https://www.cnblogs.com/liuwenwu9527/p/11108611.html) 文件上传 ...

  5. openswan源码ubantu下编译、安装、基本环境搭建

    openswan的编译过程 文章目录 openswan的编译过程 1. 下载源码: 2. 在虚拟机上解压后编译: 2.1 查看INSTALL文件 2.2 查看文件buildlin.sh文件 3. 查看 ...

  6. shell脚本获取文件名、路径名、文件类型

    1. 从字符串获取指定内容 从字符串中提取特定的信息,常用于获取文件名.文件类型.所在路径等. 1.1 获取字符串信息 用指定的方式(PATTERN)从字符串(PARAMETERS)中移除内容 &qu ...

  7. window 日志的查看与清理

    日志查看: 启动Windows实验台,点击:开始 - 控制面板 - 管理工具 - 事件查看器.如下图所示. 2.在事件查看器中右键应用程序(或安全性.系统.DNS服务器)查看属性可以得到日志存放文件的 ...

  8. vue中如何深度监听一个对象?

    大家都知道,Vue项目中对数据的监听,提供了一个很好的钩子watch,watch可以极其方便的监听我们常用数据类型值的变化,但通常当我们想监听一个对象中,某个属性值的变化时,很难达到我们预期的效果.那 ...

  9. 【多线程】Android多线程学习笔记——线程池

    Java线程池采用了享元设计模式,在系统中维持一定数量的线程,用于处理异步或并发需求,在平时处理异步或并发任务时被广泛使用.这里基于JDK1.8和Android28来整理一些关于线程池的知识点. 一. ...

  10. java线程day-02

    1.什么是线程 * 线程是程序执行的一条路径, 一个进程中可以包含多条线程 * 多线程并发执行可以提高程序的效率, 可以同时完成多项工作 2.多线程的应用场景 * 红蜘蛛同时共享屏幕给多个电脑 * 迅 ...