打出暴搜后有些茫然,不知道该怎么优化才好,看了题解才豁然开朗。
简单说下暴搜的思路:参数有\(pos,limit,lcm,num\)。其中\(lcm\)表示到\(pos+1\)位,所有非\(0\)位的\(lcm\)是多少;\(num\)表示填到\(pos+1\)位的整个数是多少。然后在\(pos=0\)时判断\(lcm\)是否整除\(num\),是则返回\(1\),否则返回\(0\),然后一层层累加。
优化思路其实很简单:
显然直接用\(f[pos][num][lcm]\)来记忆化,无论时间还是空间都是过不去的。
但我们知道所有非\(0\)位整除\(num\),就等价于它们的\(lcm\)整除\(num\)。
我们想知道\(num\)是否被\(2\)整除,那么我们关注它\(mod\ 2\)的值是否为\(0\);
我们想知道\(num\)是否被\(6\)整除,那么我们关注它\(mod\ 6\)的值是否为\(0\)。
……
那么我们想知道\(num\)是否被\(a_1,a_2,…,a_k\)整除,那么我们只需要关注它\(mod\ lcm\{a_1,a_2,…,a_k\}\)的值是否为\(0\)。
那么我们只需要记录\(num\ mod\ lcm\{1,2,…,9\}\)的值即\(num\ mod\ 2520\)即可。
\(f[pos][num][lcm]\)空间共\(20*2520*2520\)。而250MB的空间限制还是不允许我们开那么大。怎么继续优化呢?
注意到\(lcm\)中我们能用到的一共就那么几个。我们知道\(2520\)一共有\(48\)个因数。而我们\(1\sim 9\)能凑出的数的个数肯定比\(48\)少了。因此我们把质因数离散化一下,第三维开\(50\)绰绰有余。
我的代码和离散化的意思差不多,大概是现读现存的感觉,用map
存下来每个质因数存在哪个位置上。
(不知道为什么用了unsigned long long
,其实不需要)
点击查看代码
#include<bits/stdc++.h>
#define int unsigned long long
using namespace std;
int t,l,r,a[30],f[30][2520][50];
bool vis[30][2520][50];
map<int,int> ma;
int val[50],cnt;
int __lcm(int a,int b){
return a/__gcd(a,b)*b;
}
int dfs(int pos,bool limit,int lcm,int num){
if(pos==0){
if(num%lcm==0) return 1;
return 0;
}
if(!limit&&ma.find(lcm)!=ma.end()&&vis[pos][num][ma[lcm]]) return f[pos][num][ma[lcm]];
int rig=limit?a[pos]:9,ans=0;
for(int i=0;i<=rig;i++){
ans+=dfs(pos-1,limit&&i==rig,i?__lcm(lcm,i):lcm,(num*10+i)%2520);
}
if(!limit){
if(ma.find(lcm)==ma.end()) ma[lcm]=++cnt,val[cnt]=lcm;
f[pos][num][ma[lcm]]=ans,vis[pos][num][ma[lcm]]=1;
}
return ans;
}
int solve(int x){
int len=0;
while(x){
a[++len]=x%10;
x/=10;
}
return dfs(len,1,1,0);
}
signed main(){
cin>>t;
memset(vis,0,sizeof vis);
while(t--){
cin>>l>>r;
cout<<solve(r)-solve(l-1)<<endl;
}
return 0;
}