[題解]CF55D Beautiful Numbers

Sinktank發表於2024-04-14

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;
}

相關文章