CF55D Beautiful Numbers
打出暴搜後有些茫然,不知道該怎麼最佳化才好,看了題解才豁然開朗。
簡單說下暴搜的思路:引數有\(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;
}