题解 P5842 【[SCOI2012]Blinker 的仰慕者】
Update on 2020.5.31:极大简化了代码,跑的飞快
小蒟蒻的第一篇题解,望大家支持,如有排版格式问题还请见谅。
此题比较复杂,做的话需要先熟悉数位dp的基本套路,我们慢慢分析。
看到 “各位数字之积” 应该可以想到这是数位dp题,那么最朴素的想法就是
考虑到 K 为 1-9 这些数字的乘积(我们先忽略0),K的质因子就只有 2,3,5,7 四个,因此我们可以用四个下标 a , b , c , d 来表示
为了减少复杂度我们先预处理出 反正数很少),如下:
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);
为了转移方便,某个
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);
}
其中
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状态就可以设计为
我们再关注一下这个题要求什么:满足题意数的和。如果求个数的话就很简单了,和怎么求呢?我们还要加入一个数组g,
而后面这些可能的XXX的总和恰是下一位的
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;
}
剩余的数位乘积是减少的,
需要
if(f[k][rest]==-1)dp(k,rest);
那么对于输入的A,B,K,先转化成前缀相减:
cout<<((solve(r)-solve(l-1))%mod+mod)%mod<<endl;
在上界的限制下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;
}
这个题做完了?似乎我们还忽略了零。。。
如果和上面那些比这还不简单)与
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;
}