题意
给定一个数n
说明有从 0 到 9999...(n个9)的10^n个数字
所有数字如果不及n位,全部用前导0填充,以保证每个数字都是n位数
然后定义每个数字是由多个块组成的
相邻的相同数字构成一个块
以 00027734000 为例
共有3个长度为1的块(2,3,4)
1个长度为2的块(77)
2个长度为3的块(000,000)
问,这10^n个n位数中,不同长度的块总和共有多少个
输入
一个数字n
1 <= n <= 2e5
输出
n个数字,以空格隔开
第 i 个数表示长度为 i 的块的个数
答案对998244353取模
解题思路
下面讲的是递推的方法
假设现在的n为1,即只有0~9这10个数
那么如果给每个数加一位,让1位数变成2位数
可以得到,长度为2的块只能由长度为1的块旁边加一个跟它一样的数字来得到
所以2位数中,长度为2的块就是10种,00,11,22 ... 99
而长度为1的块个数,就是剩下的所有位的个数
即所有两位数的位数和为 2(每个数字2位) * 100(种) = 200
长度为2的块占用了 2(位)*10(种)= 20
所以长度为1的块有 200 - 20 = 180 种
同理,从2位数往3位数递推
长度为3的块只能由长度为2的块旁边加一个相同的数字得到
所以长度为3的块种类数为10
长度为2的块只能由长度为1的块旁边加一个相同数字得到
注意:
块不能从相同长度的块保持状态转移而来
因为如果想从相同长度的块转移而来,那么转移多出来的数字就不能加在这个块旁边。
但是只要不加在这个块旁边的话,得到的数字又能从其他状态中转移而来,造成重复
举个例子,12223中的222是个长度为3的块
如果要保留这个块长度,则多出来的数字不能加在222的左右
以在后面加个1为例,就会变成122231
但是这个数字又能从12231的块22加个2转移而来
所以会造成统计重复,故只能把块往更长的长度转移
所以长度为2的块种类数为180
三位数共有 3 * 1000 = 3000 位
所以长度为1的块种类数为 3000 - 180 * 2 - 10 * 3 = 2610 种
以此类推,直到推到需要的n位数即可
可以发现1位数所有位个数和为10,2位数所有位个数和为200,3位数所有位个数和为3000……
则 i 位数所有位个数和为 i*(10^i)
位数为 i 时长度为 1 的块个数的递推式为
i * (10^i) - 2(长度为2的块个数)- 3(长度为3的块个数)- ... - n(长度为n的块个数)
代码实现
各长度块个数用一个数组ans存起来,使用long long类型
两位数的情况可以先手打出来
ans[1]=10;
ans[2]=180;
然后从3开始循环到n求ans数组
但是如果真的一步步都去算上述递推式中的 位数*个数 之和的话
时间复杂度将会是O(n^2)级别,对于1e5的范围会超时
所以换种方法去想(疯狂寻找高中的奇怪的知识)
根据上面提到的,每一个2位及以上的块都是以低一位的块加一个相同数字转移而来
所以这里面总共加的数字就是上一个状态情况数的总和
则当前状态长度为1的块个数就可以由 **(长度为i的所有数字的位个数) - (长度为i-1的所有数字的位个数) - (长度为i-1时所有块个数之和) **得到
则再加个sum变量存ans数组的前缀和就能实现O(n)的解法
对于 i*(10^i) ,可以预处理所有 10^i 存在数组里方便调用
也可以直接加上快速幂
下文用的是存数组的办法,数组名e10
所以现在的ans[i]转移方程就是 i * e10[i] - (i-1) * e10[i-1] - sum
又因为数据过大需要取模
假设此时e10[i]很小,e10[i-1]和sum很大,那么为了保证能变成正数后再取模
所以要再加上 i 个 mod
最后的表达式为
ans[i]=(i*e10[i]-(i-1)*e10[i-1]-sum+i*mod)%mod;
对于前缀和sum
在其后跟一句
sum=(sum+ans[i])%mod;
即可
最后将ans数组反序输出
完整代码
(78ms / 2000ms)
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll mod=998244353;
ll ans[200050],e10[200050];
int main(){
int n,i;
scanf("%d",&n);
e10[0]=1;
for(i=1;i<=n;i++)
e10[i]=e10[i-1]*10%mod;
ll sum=190;
ans[1]=10;
ans[2]=180;
for(i=3;i<=n;i++)
{
ans[i]=(i*e10[i]-(i-1)*e10[i-1]-sum+i*mod)%mod;
sum=(sum+ans[i])%mod;
}
for(i=n;i;i--)
printf("%lld ",ans[i]);
return 0;
}