1 条题解

  • 0
    @ 2025-8-24 22:06:02

    自动搬运

    查看原文

    来自洛谷,原作者为

    avatar jijidawang
    And in that light, I find deliverance.

    搬运于2025-08-24 22:06:02,当前版本为作者最后更新于2020-05-18 22:37:47,作者可能在搬运后再次修改,您可在原文处查看最新版

    自动搬运只会搬运当前题目点赞数最高的题解,您可前往洛谷题解查看更多

    以下是正文


    数位 dp。

    dpq,idp_{q,i}i{0,1,2,3,4,5,6,7,8,9}i\in\{0,1,2,3,4,5,6,7,8,9\})为 1q1\sim qii 出现的次数,1q1\sim q 的数字和显然就是 $dp_{q,0}\times 0+dp_{q,1}\times 1+\cdots+dp_{q,i}\times i\cdots+dp_{q,9}\times 9$。

    所以我们只需要求出 1q1\sim qii 出现的次数就能解决这个问题了。

    这个问题看起来很好解决,但是注意前导零会影响结果,所以不能有前导零。

    这该怎么办呢?

    有前导零的式子很容易推出。有 qq 位数字,ii 数码的出现次数对于 x{ssN,10qs10q+1}x\in\{s\mid s\in \mathbb N,10^q\le s\le10^{q+1}\}f(q,i)f(q,i) 的数量都是相等的(设 f(q,i)f(q,i)qq 位数 ii 数码的出现次数)。

    具体求法罢,是:
    $\begin{cases}f(q,i)=0&q=0\\f(q,i)=10f(q-1)+10^{q-1}&q>0\end{cases}$

    我们考虑减去多余的 00

    我们先设数字为 A1A2A3An\overline{A_1A_2A_3\dots A_n}

    我们首先考虑求 A1000\overline{A_100\dots 0},将 A1000\overline{A_100\dots 0} 分割为区间 $[0000,1000),[1000,2000),\dots,[\overline{(A_1-1)00\dots 0},\overline{A_100\dots 0})$,所以答案就为 10n1A110^{n-1}A_1,注意 <A1<A_1 的每个数还出现了 10n110^{n-1} 次,所以要加上。

    首位 A1A_1 出现了 A2A3An+1\overline{A_2A_3\dots A_n}+1 次,答案还要加上 A2A3An+1\overline{A_2A_3\dots A_n}+1

    当然还需要处理前导 00,用排列组合算一下会知道 iiqq 个前导零的数量就是 10q10^qq{ssN,0si1}q\in\{s\mid s\in\mathbb N,0\le s\le i-1\}),把它们加起来会发现一共出现了 10i1+10i2+...1010^{i-1}+10^{i-2}+...10k=0i110k\sum\limits_{k=0}^{i-1}10^k) 次,减一下即可。

    Code:

    #include<iostream>
    #include<cstring>
    using namespace std;
    typedef long long ll;
    const int N=51,MOD=1e9+7; //注意能 MOD 的地方都要 MOD,不然会 WA 0pts。
    ll pow10[N],dp[N],a[N],count[N],tmpcount[N],ans;
    // pow10    : 字面意思,10^n
    // dp       : 不考虑前导零的状况
    // count    : 统计 0~9 出现次数
    // tmpcount : 暂时保存 count,用来减
    // ans      : 累加答案
    void init() //预处理 pow10 和 dp。
    {
    	pow10[0]=1;
    	for (int i=1;i<30;i++) dp[i]=(dp[i-1]*10%MOD+pow10[i-1])%MOD,pow10[i]=10*pow10[i-1]%MOD;
    }
    void solve(ll x)
    {
    	int len=0;
    	while (x){a[++len]=x%10;x/=10;} //数位分离
    	for (int i=len;i>=1;i--)        //从高到低遍历
    	{
    		for (int j=0;j<10;j++) count[j]+=dp[i-1]*a[i],count[j]%=MOD;  //分割区间
    		for (int j=0;j<a[i];j++) count[j]+=pow10[i-1],count[j]%=MOD; //加上 10^(n-1)
    		ll lastnum=0;
    		for (int j=i-1;j>=1;j--) lastnum=lastnum*10+a[j],lastnum%=MOD; //求出 A2A3A4...An
    		count[a[i]]+=lastnum+1,count[a[i]]%=MOD;
    		count[0]-=pow10[i-1],count[0]=(count[0]+MOD)%MOD; //减去前导零
    	}
    }
    int main()
    {
    	init();
    	ll l,r,T;
    	cin>>T;
    	for (int q=0;q<T;q++)
    	{
    		ans=0; cin>>l>>r;
    		solve(r); //前缀和思想相减 r 和 l-1。
    		for (int i=0;i<10;i++) (tmpcount[i]=count[i]),count[i]=0; //复制 count,记得清零
    		solve(l-1);
    		for (int i=0;i<10;i++) ans=(ans+i*(tmpcount[i]-count[i]+MOD)%MOD)%MOD,count[i]=0; //累加答案,记得清零 count。
    		cout<<ans<<'\n';
    	}
    	return 0;
    }
    

    Refence 求数字 ii 出现的次数

    • 1

    信息

    ID
    3998
    时间
    1000ms
    内存
    125MiB
    难度
    4
    标签
    递交数
    0
    已通过
    0
    上传者