牛客不降数——矩阵快速幂应用

不降数
首先暴力打表,寻找规律;

/*
1 1 1 1 1 1 1 1 1 (0~9)
9 8 7 6 5 4 3 2 1 (10~99)
45 36 28 21 15 10 6 3 1 (100~999)
165 120 84 56 35 20 10 4 1 (1000~9999)
495 330 210 126 70 35 15 5 1 (10000~99999)
*/
以第二行为例,910~19的总数;820~29的总数;
以第三行为例,45100~199的总数;以此类推

我们容易发现,下一行的第一位,就是上一行的总和;

下一行的第二位,就是上一行的第二位到第九位的总和;

以此类推;

然后我们就可以写出这样的一段代码

#include <iostream>
#include <cstring>
#include <cstdio>
using namespace std;
int f[15];
const int MOD = 100019;
/*
1 1 1 1 1 1 1 1 1
9 8 7 6 5 4 3 2 1
45 36 28 21 15 10 6 3 1
165 120 84 56 35 20 10 4 1
495 330 210 126 70 35 15 5 1
*/
int read(){
    int x=0;bool f=0;char c=getchar();
    while (c<'0'||c>'9'){if (c=='-')f=1;c=getchar();}
    while (c>='0'&&c<='9'){x=(x<<1)+(x<<3)+(c^48);c=getchar();}
    return f?-x:x;
}

void write(int x)
{
     if(x<0) putchar('-'),x=-x;
     if(x>9) write(x/10);
     putchar(x%10+'0');
}

int main()
{
    int n;
    n=read();
    if(n<2) {
        putchar('9');
    }else{
        for(int i=1;i<=9;i++) f[i]=10-i;
        int sum = 0;
        for(int i=2;i<=n;i++){
            sum = 0;
            for(int j=9;j>=1;j--){
                //算上一行的和 填下一行的值
                sum+=f[j];
                f[j]=sum%MOD;
            }
        }
        write(f[1]);
    }
    return 0;
}

然后T了,,快读快写都用上了…

本来以为能水过去的,毕竟数据是10^8还是有机会的;

于是查看题解,发现了可以用矩阵快速幂来加速;

首先构造矩阵

在这里插入图片描述

B就是0~9分别对应的9个数,我们发现B左乘一次A;
就得到了10~99对应的9个数;
再左乘一次A,就得到了100~999对应的9个数

/*
1 1 1 1 1 1 1 1 1 (0~9)
9 8 7 6 5 4 3 2 1 (10~99)
45 36 28 21 15 10 6 3 1 (100~999)
165 120 84 56 35 20 10 4 1 (1000~9999)
495 330 210 126 70 35 15 5 1 (10000~99999)
*/

因为题目要求的是n位的总和;

那么我们只需要计算A^(n-1)×B

就可以得到一个9×1列的矩阵,然后累加这九个数字,就是答案了;

Code

#include <iostream>
#include <cstring>
#include <cstdio>
using namespace std;
const int MOD = 100019;
typedef long long ll;
/*
1 1 1 1 1 1 1 1 1 0~9
9 8 7 6 5 4 3 2 1 10~99
45 36 28 21 15 10 6 3 1 100~999
165 120 84 56 35 20 10 4 1 1000~9999
495 330 210 126 70 35 15 5 1 10000~99999
*/
struct Mat{
    ll c[15][15];
}E,A,B;
Mat mul(Mat x,Mat y){
    Mat ret;
    for(int i=1;i<=9;i++)
        for(int j=1;j<=9;j++) ret.c[i][j]=0;
    for(int i=1;i<=9;i++){
        for(int j=1;j<=9;j++){
            for(int k=1;k<=9;k++){
                ret.c[i][j]=(ret.c[i][j]%MOD+x.c[i][k]*y.c[k][j]%MOD)%MOD;
            }
        }
    }
    return ret;
}
//x^y
Mat q_pow(Mat x,ll y){
    Mat ret = E;
    Mat base = x;
    while(y){
        if(y&1){
            ret=mul(ret,base);
        }
        base=mul(base,base);
        y>>=1;
    }
    return ret;
}
void print_Mat(Mat A){
    for(int i=1;i<=9;i++){
        for(int j=1;j<=9;j++){
            cout << A.c[i][j] << ' ';
        }
        cout << '\n';
     }
}
void init(){
    for(int i=1;i<=9;i++)
        for(int j=i;j<=9;j++)
            A.c[i][j]=1;
    for(int i=1;i<=9;i++) B.c[i][1]=E.c[i][i]=1;
}
int main()
{
    init();
    int n;
    cin >> n;
    Mat powA =q_pow(A,n-1);
    B = mul(powA,B);
    ll ans = 0;
    for(int i=1;i<=9;i++) {
        ans=ans+B.c[i][1];
        ans%=MOD;
    }
    cout << ans << '\n';
    return 0;
}

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值