P4999烦(gui)人(chu)的数学作业

P4399P4999

这是一道有着三倍经验的宝藏题目
我们可以求出来1到n中,1~9分别出现了几次,设f[i]为数字i出现的次数,则(ans=sum{f[i]cdot i})

然后就是数位dp干的事了

我们可以在dp时统计当前要求的数goal出现的次数sum,到达边界时返回sum即可。注意考虑前导零

#include<iostream>
#include<cstdio>
#include<cmath>
#include<algorithm>
#include<queue>
#include<cstring>
using namespace std;
typedef unsigned long long ull;
typedef long long ll;
const int inf=214748364;
const ll mod=1000000007;//注意是1e9+7
inline ll read()
{
    char ch=getchar();
    ll x=0;bool f=0;
    while(ch<'0'||ch>'9')
    {
        if(ch=='-')f=1;
        ch=getchar();
    }
    while(ch>='0'&&ch<='9')
    {
        x=(x<<1)+(x<<3)+(ch^48);
        ch=getchar();
    }
    return f?-x:x;
}
ll l,r;
ll f[10],g[20][250],li[20];
int t;
void make(ll k)
{
    t=0;
    while(k)
    {
        li[++t]=k%10;
        k/=10;
    }
}
ll dfs(int now,int sum,int goal,bool lim,bool al)//al记录是否前面全是0
{
    if(!now)return sum;
    if(!al&&!lim&&g[now][sum]!=-1) return g[now][sum];
    int up=lim?li[now]:9;
    ll rtn=0;
    for(int i=0;i<=up;i++)
    {
       rtn+=dfs(now-1,sum+((i||(!al))&&(i==goal)),goal,lim&&(i==up),al&&(!i));//这里sum的计算方式是在考虑goal是0的时候,排除前导零的影响(在另外那两倍经验那里也适用)
       rtn=(rtn+mod)%mod;
	}
    if(!lim&&!al) g[now][sum]=rtn;
    return rtn;
}
int main()
{
   int fk=read();
   while(fk--)
   {
   	ll ans=0;
   l=read();
   r=read();
   make(r);
   for(int i=1;i<=9;i++)
    memset(g,-1,sizeof(g)),f[i]=dfs(t,0,i,1,1); 
   if(l>1)
    {
    make(l-1);
    for(int i=1;i<=9;i++)
    memset(g,-1,sizeof(g)),f[i]-=dfs(t,0,i,1,1);
    }
   for(int i=1;i<=9;i++) 
	ans=(ans+((f[i]%mod)*i+mod)%mod+mod)%mod;
   printf("%lld
",ans);
   }
}

另外的两倍经验是P2602和P1239
手动滑稽

原文地址:https://www.cnblogs.com/lcez56jsy/p/11482850.html