- 定义一个数的自积为这个数乘上它的所有数码,求自积在([l,r])范围内的数的个数。
- (Ale Ble10^{18})
可能的数位积
考虑某一位数码上最多只可能有(3)个(2)、(2)个(3)、(1)个(2)和(1)个(3)、(1)个(5)、(1)个(7)。
因此我们可以直接枚举(2,3,5,7)各自的个数,求出至少需要多少位才可能存在这样一个数位积,那么至少需要位数超出(18)位的肯定直接不用管了。
剩余的可能的数位积个数实际上并不多,实测只有(36100)个。
数位(DP)
首先是数位(DP)的经典差分,用([1,r])的答案减去([1,l-1])的答案,那么就相当于只要求解([1,n])形式的答案。
直接枚举数位积(v_i),然后就是要求(lfloor1,lfloorfrac n{v_i} floor])范围内有多少个数的数位积是(v_i)。
先枚举从哪一位开始(因为可能有前导(0)),然后(DP)。
设(f_{x,s,0/1})表示处理到第(x)位,剩余需要的数位积编号为(s),是否严格小于(n)的方案数。
转移时枚举当前位填的数,首先不能超出这一位的限制,其次必须是(v_s)的因数。
代码:(O()能过())
#include<bits/stdc++.h>
#define Tp template<typename Ty>
#define Ts template<typename Ty,typename... Ar>
#define Reg register
#define RI Reg int
#define Con const
#define CI Con int&
#define I inline
#define W while
#define V 36100
#define LL long long
using namespace std;
LL l,r,v[V+5],d[V+5];int tot;map<LL,int> id;
int ct,a[20];I void Init(LL x) {ct=0;W(x) a[++ct]=x%10,x/=10;}
LL f[20][V+5];I LL DP(CI x,CI s,CI fg=0)
{
if(!x) return v[s]==1;if(d[s]>x) return 0;if(fg&&~f[x][s]) return f[x][s];//DP完;位数不够;记忆化
LL t=0;for(RI i=1,l=fg?9:a[x];i<=l;++i) !(v[s]%i)&&(t+=DP(x-1,id[v[s]/i],fg||(i^l)));//枚举当前位的数码
return fg&&(f[x][s]=t),t;//记忆化
}
I LL Calc(Con LL& n)
{
LL t=0;for(RI i=1,j;i<=tot;++i) for(Init(n/v[i]),j=ct;j>=d[i];--j) t+=DP(j,i,j!=ct);return t;//枚举数位积,枚举最高位
}
int main()
{
RI i,j,k,p,t;for(i=0;i<=3*18;++i) for(j=0;j<=2*18;++j) for(k=0;k<=18;++k) for(p=0;p<=18;++p)//枚举2,3,5,7个数
(t=i/3+(i%3>0)+j/2+(j%2>0)-(i%3==1&&j%2==1)+k+p)<=18&&(d[++tot]=t,id[v[tot]=(LL)pow(2,i)*pow(3,j)*pow(5,k)*pow(7,p)]=tot);//计算最少位数
return memset(f,-1,sizeof(f)),scanf("%lld%lld",&l,&r),printf("%lld
",Calc(r)-Calc(l-1)),0;//差分
}