• Codeforces 1327E


    题意

    给定一个数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;
    }
    

  • 相关阅读:
    20165319第五周java学习笔记
    20165319 20165335 结对学习创意照
    20165215 结对编程——四则运算第二周
    20165215 2017-2018-2 《Java程序设计》第八周学习总结
    2017-2018-2 20165215 实验二 Java面向对象程序设计
    20165215 结对编程——四则运算第一周
    20165215 2017-2018-2 《Java程序设计》第7周学习总结
    20165215 2017-2018-2 《Java程序设计》第6周学习总结
    20165215 实验一 Java开发环境的熟悉
    20165215 2017-2018-2 《Java程序设计》第5周学习总结
  • 原文地址:https://www.cnblogs.com/stelayuri/p/12556495.html
Copyright © 2020-2023  润新知