Codeforces 1327E - Count The Blocks

题意

给定一个数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("%d ",ans[i]);
	
	return 0;
}

猜你喜欢

转载自www.cnblogs.com/stelayuri/p/12556495.html
今日推荐