1 条题解
-
0
自动搬运
来自洛谷,原作者为

slgdsxw
**搬运于
2025-08-24 22:14:53,当前版本为作者最后更新于2020-04-01 01:31:15,作者可能在搬运后再次修改,您可在原文处查看最新版自动搬运只会搬运当前题目点赞数最高的题解,您可前往洛谷题解查看更多
以下是正文
Update on 2020.5.31:极大简化了代码,跑的飞快
小蒟蒻的第一篇题解,望大家支持,如有排版格式问题还请见谅。
此题比较复杂,做的话需要先熟悉数位dp的基本套路,我们慢慢分析。
看到 “各位数字之积” 应该可以想到这是数位dp题,那么最朴素的想法就是 表示前 位数各位数字之积为 时的答案, 最大,无法通过。
考虑到 K 为 1-9 这些数字的乘积(我们先忽略0),K的质因子就只有 2,3,5,7 四个,因此我们可以用四个下标 a , b , c , d 来表示 ,即 ,但是把四个参数都写到dp数组里的话状态数还是有点大,达到了 1e8 的级别。
为了减少复杂度我们先预处理出 所有以内所有可能的 () ,它可以很暴力(
反正数很少),如下:int lim2=59,lim3=37,lim5=25,lim7=21; ll fac2[60],fac3[60],fac5[60],fac7[60]; fac2[0]=fac3[0]=fac5[0]=fac7[0]=1; fac[0]=1; for(int i=1;i<=lim2;i++)fac2[i]=fac2[i-1]*2; for(int i=1;i<=lim3;i++)fac3[i]=fac3[i-1]*3; for(int i=1;i<=lim5;i++)fac5[i]=fac5[i-1]*5; for(int i=1;i<=lim7;i++)fac7[i]=fac7[i-1]*7; for(int i=1;i<=18;i++)fac[i]=fac[i-1]*10; maxk=fac[18]; for(int i=1;i<=18;i++)fac[i]%=mod; for(int i=0;i<=lim2;i++) for(int j=0;j<=lim3&&fac2[i]*fac3[j]<=maxk;j++) for(int k=0;k<=lim5&&fac2[i]*fac3[j]*fac5[k]<=maxk;k++) for(int l=0;l<=lim7&&fac2[i]*fac3[j]*fac5[k]*fac7[l]<=maxk;l++) num[++kcnt]=fac2[i]*fac3[j]*fac5[k]*fac7[l]; sort(num+1,num+kcnt+1);为了转移方便,某个 与1-9中的某数运算的结果在数表num中的位置我们也需要知道,预处理出来, 对应着2,3,5,7。
for(int i=1;i<=kcnt;i++)to[i][1]=i; for(int i=0;i<=3;i++) for(int j=1;j<=kcnt;j++) if(num[j]%p[i]==0)to[j][p[i]]=id(num[j]/p[i]); for(int i=4;i<=9;i++) { if(i==p[0]||i==p[1]||i==p[2]||i==p[3])continue; for(int j=1;j<=kcnt;j++)to[j][i]=getto(j,i); }其中 表示数表中第 个数除以 的结果在数表中的位置(注意是除不是乘,原因下面讲)id函数使用的stl的二分查找,getto是质因数分解求to的过程,如 。 代码如下:
int id(ll x) { return lower_bound(num+1,num+kcnt+1,x)-num; } int getto(int x,int k) { for(int i=0;i<=3;i++) while(k%p[i]==0) { k/=p[i]; x=to[x][p[i]]; } return x; }然后我们的dp状态就可以设计为 表示前 位当前乘积为 的答案数,因为如果没有数的大小限制,且没有前导零的影响,的值就只与最终数位的乘积K有关系。我们把的含义改一下,表示到了第 位,还需要的数位乘积为 的答案,那么 的值与K也没有了关系(把K当初始值来看)。我们求答案用到 的时候已经有小于上界和无前导零的条件,这两维不用考虑。
我们再关注一下这个题要求什么:满足题意数的和。如果求个数的话就很简单了,和怎么求呢?我们还要加入一个数组g,的最终含义:表示到了第 位,还需要的数位乘积为 时的方案数: 表示到了第 位,还需要的数位乘积为 时可以填的数的和(中间两维我略了)。这个 指的数是从第 位开始计的,比如,后三位可以填123,213,321这三个数,,那么的值为3, 的值即为
如何转移呢? 的值可以直接从下一位累加,假设第 位我们填了a,后面还有3位, 计算的和即为形如aXXX这样的数的和,这个XXX每有一种方案, 就加上 而后面这些可能的XXX的总和恰是下一位的 要所表示的,XXX的方案数则由下一位的 表示,那么这个预先dp就可以写出了(边界值按的定义很好考虑, 为:
void dp(int k,int rest) { if(!k) { f[k][rest]=(rest==1); g[k][rest]=0; return; } if(f[k][rest]!=-1)return; ll rf=0,rg=0; int a1,a2; for(int i=9;i>=1;i--) { a1=k-1; a2=(i==0)?rest:to[rest][i]; if(!a2)continue; dp(a1,a2); rf+=f[a1][a2]; rg=(rg+i*f[a1][a2]*fac[k-1]+g[a1][a2])%mod; } f[k][rest]=rf%mod; g[k][rest]=rg; return; }剩余的数位乘积是减少的,在这里就用上了(这就是为什么处理的是除)。
需要 的值时调用这个dp:
if(f[k][rest]==-1)dp(k,rest);那么对于输入的A,B,K,先转化成前缀相减:
cout<<((solve(r)-solve(l-1))%mod+mod)%mod<<endl;在上界的限制下dfs每一位填什么,并传递当前前缀值,如果满足已经小于上界、没有前导零影响了,直接返回答案,计算方法与 的推导类似,可以把当前的前缀值pre看做我们刚才讨论 时的a。dfs代码如下:
ll dfs(int k,int low,int zero,int rest,ll pre) { if(!rest)return 0; if(low&(!zero)) { if(f[k][rest]==-1)dp(k,rest); return (pre*fac[k]%mod*f[k][rest]+g[k][rest])%mod; } if(!k)return pre*(rest==1); ll res=0; for(int i=low?9:a[k];i>=(!zero);i--) res+=dfs(k-1,low|(i<a[k]),zero&(i==0),(i==0)?rest:to[rest][i],(pre*10+i)%mod); return res; }这个题做完了?似乎我们还忽略了零。。。
如果 ,问题可以看做求在A到B中所有至少有一位为零的数的和,(
和上面那些比这还不简单)与的定义类似,表示到了第 位 是(1)否(0)小于上界值,是(1)否(0)有前导零, 前面已经有 个零时的填的方案数, 表示填的数的和,注意前导零。void dp0(int k,int low,int zero,int cnt0) { if(!k) { f0[k][low][zero][cnt0]=(cnt0>0); g0[k][low][zero][cnt0]=0; return; } if(f0[k][low][zero][cnt0]!=-1)return; ll rf=0,rg=0; int b1,b2,b3,b4; for(int i=low?9:a[k];i>=0;i--) { b1=k-1; b2=low|(i<a[k]); b3=zero&(i==0); b4=cnt0+((!zero)&(i==0)); dp0(b1,b2,b3,b4); rf=(rf+f0[b1][b2][b3][b4])%mod; rg=(rg+i*f0[b1][b2][b3][b4]*fac[k-1]%mod+g0[b1][b2][b3][b4])%mod; } f0[k][low][zero][cnt0]=rf; g0[k][low][zero][cnt0]=rg; return; }还有一些细节如判断K是否可以被2,3,5,7质因数分解等不一一赘述了,看完整代码吧,少用些取模运算速度提升很大。
感觉自己写的复杂度很高,常数很大,有可优化之处还望各位dalao赐教qwq。
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #include<ctime> #define ll long long using namespace std; const int mod=20120427; int kcnt; int to[70000][10],a[20],p[4]={2,3,5,7}; ll l,r,K,maxk; ll fac[20],num[70000],f[20][70000],g[20][70000]; ll f0[20][2][2][20],g0[20][2][2][20]; int id(ll x) { return lower_bound(num+1,num+kcnt+1,x)-num; } int getto(int x,int k) { for(int i=0;i<=3;i++) while(k%p[i]==0) { k/=p[i]; x=to[x][p[i]]; } return x; } void dp(int k,int rest) { if(!k) { f[k][rest]=(rest==1); g[k][rest]=0; return; } if(f[k][rest]!=-1)return; ll rf=0,rg=0; int a1,a2; for(int i=9;i>=1;i--) { a1=k-1; a2=(i==0)?rest:to[rest][i]; if(!a2)continue; dp(a1,a2); rf+=f[a1][a2]; rg=(rg+i*f[a1][a2]*fac[k-1]+g[a1][a2])%mod; } f[k][rest]=rf%mod; g[k][rest]=rg; return; } void init() { int lim2=59,lim3=37,lim5=25,lim7=21; ll fac2[60],fac3[60],fac5[60],fac7[60]; fac2[0]=fac3[0]=fac5[0]=fac7[0]=1; fac[0]=1; for(int i=1;i<=lim2;i++)fac2[i]=fac2[i-1]*2; for(int i=1;i<=lim3;i++)fac3[i]=fac3[i-1]*3; for(int i=1;i<=lim5;i++)fac5[i]=fac5[i-1]*5; for(int i=1;i<=lim7;i++)fac7[i]=fac7[i-1]*7; for(int i=1;i<=18;i++)fac[i]=fac[i-1]*10; maxk=fac[18]; for(int i=1;i<=18;i++)fac[i]%=mod; for(int i=0;i<=lim2;i++) for(int j=0;j<=lim3&&fac2[i]*fac3[j]<=maxk;j++) for(int k=0;k<=lim5&&fac2[i]*fac3[j]*fac5[k]<=maxk;k++) for(int l=0;l<=lim7&&fac2[i]*fac3[j]*fac5[k]*fac7[l]<=maxk;l++) num[++kcnt]=fac2[i]*fac3[j]*fac5[k]*fac7[l]; //cout<<kcnt<<endl; sort(num+1,num+kcnt+1); for(int i=1;i<=kcnt;i++)to[i][1]=i; for(int i=0;i<=3;i++) for(int j=1;j<=kcnt;j++) if(num[j]%p[i]==0)to[j][p[i]]=id(num[j]/p[i]); for(int i=4;i<=9;i++) { if(i==p[0]||i==p[1]||i==p[2]||i==p[3])continue; for(int j=1;j<=kcnt;j++)to[j][i]=getto(j,i); } memset(f,-1,sizeof(f)); return; } ll check(ll x) { for(int i=0;i<=3;i++) while(x%p[i]==0)x/=p[i]; return x; } ll dfs(int k,int low,int zero,int rest,ll pre) { if(!rest)return 0; if(low&(!zero)) { if(f[k][rest]==-1)dp(k,rest); return (pre*fac[k]%mod*f[k][rest]+g[k][rest])%mod; } if(!k)return pre*(rest==1); ll res=0; for(int i=low?9:a[k];i>=(!zero);i--) res+=dfs(k-1,low|(i<a[k]),zero&(i==0),(i==0)?rest:to[rest][i],(pre*10+i)%mod); return res; } void dp0(int k,int low,int zero,int cnt0) { if(!k) { f0[k][low][zero][cnt0]=(cnt0>0); g0[k][low][zero][cnt0]=0; return; } if(f0[k][low][zero][cnt0]!=-1)return; ll rf=0,rg=0; int b1,b2,b3,b4; for(int i=low?9:a[k];i>=0;i--) { b1=k-1; b2=low|(i<a[k]); b3=zero&(i==0); b4=cnt0+((!zero)&(i==0)); dp0(b1,b2,b3,b4); rf=(rf+f0[b1][b2][b3][b4])%mod; rg=(rg+i*f0[b1][b2][b3][b4]*fac[k-1]+g0[b1][b2][b3][b4])%mod; } f0[k][low][zero][cnt0]=rf; g0[k][low][zero][cnt0]=rg; return; } ll solve(ll x) { int len=0; while(x) { a[++len]=x%10; x/=10; } return dfs(len,0,1,id(K),0); } ll solve0(ll x) { int len=0; while(x) { a[++len]=x%10; x/=10; } memset(f0,-1,sizeof(f0)); dp0(len,0,1,0); return g0[len][0][1][0]; } int main() { int T; init(); cin>>T; while(T--) { cin>>l>>r>>K; if(!K) { cout<<((solve0(r)-solve0(l-1))%mod+mod)%mod<<endl; continue; } ll x=check(K); if(x!=1) { cout<<0<<endl; continue; } cout<<((solve(r)-solve(l-1))%mod+mod)%mod<<endl; } return 0; }
- 1
信息
- ID
- 4848
- 时间
- 1000ms
- 内存
- 125MiB
- 难度
- 7
- 标签
- 递交数
- 0
- 已通过
- 0
- 上传者