1. 程式人生 > 實用技巧 >題解 HDU6834 Yukikaze and Smooth numbers

題解 HDU6834 Yukikaze and Smooth numbers

題目大意

題目連結

給定\(n,k\)。我們認為一個正整數是合法的,當且僅當它所有質因數都小於等於\(k\)。求有多少小於等於\(n\)的合法的正整數。

資料範圍:\(T\)組測試資料,\(1\leq T\leq 50\)\(1\leq n,k\leq 10^9\)

本題題解

\(k\geq n\)時,顯然所有\(\leq n\)的正整數都是合法的,答案就是\(n\),我們可以特判。以下只考慮\(k<n\)的情況。


\(k>\sqrt{n}\)時,任何數只要有一個\(>k\)的質因子,都是不合法的;而此時一個小於等於\(n\)的正整數,至多隻有\(1\)\(>k\)

的質因子。換句話說,此時一個數\(x\) (\(1\leq x\leq n\))不合法當且僅當存在某個質數\(p>k\),滿足\(x\)\(p\)的倍數;並且一個\(x\)最多隻可能是一個\(p\)的倍數。所以,不合法的數的總數就是\(\sum_{i=k+1}^{n}[i\text{是質數}]\lfloor\frac{n}{i}\rfloor\),答案就是\(n\)減去這個數量。

\(k+1\)開始求和比較麻煩。先考慮求\(s(n)=\sum_{i=1}^{n}[i\text{是質數}]\lfloor\frac{n}{i}\rfloor\)

這個\(s\)函式,無論是它本身,還是它在質數處的取值,都不是積性函式,很難用數論上常用的篩法求。因此我們需要進一步轉化。仔細觀察這個式子,發現\(\lfloor\frac{n}{i}\rfloor\)

是個好東西,因為對於任意一個\(n\)\(\lfloor\frac{n}{i}\rfloor\)只有\(O(\sqrt{n})\)種不同的取值。我們可以用數論分塊列舉這些取值,並且能知道這個取值對應的區間\(l,r\),那麼問題就轉化為求\([l,r]\)區間裡有多少質數。

我們還發現一個好訊息,這裡的\([l,r]\)不是隨意的,而是總是存在一個\(i\)使得\(r=\lfloor\frac{n}{i}\rfloor\),而\(l\)就是上一個\(r\)\(1\)。這正是\(\text{min25}\)篩法的前半部分:對於所有\(r\in\{\lfloor\frac{n}{i}\rfloor\ |\ 1\leq i\leq n\}\)

,求出\(\sum_{i=1}^{r}[i\text{是質數}]f(i)\)。這裡\(f(i)\)被要求在質數處的取值是一個完全積性函式,在本題中,因為是求質數個數,我們可以認為\(f(i)=1\),顯然這個函式是完全積性的。

\(\text{min25}\)篩法的過程不詳細介紹了,不會的可以去看我寫的良心入門教程

還有最後一個小問題:我們真正要算的是從\(k+1\)開始的,而不是從\(1\)開始的,這就意味著我們的第一段\(l\),它不一定是\(\lfloor\frac{n}{i}\rfloor+1\)的形式。不過這也很好辦,我們用同樣的方法,篩出\(\leq k\)的質數個數,作為初始值即可。

時間複雜度\(O(\sqrt{n}+\frac{n^{\frac{3}{4}}}{\log n})\)


以上只是\(k>\sqrt{n}\)的情況。當\(k\leq \sqrt{n}\)時,沒有什麼特別清晰的好方法。不過我們可以先把\(\leq k\)的質數篩出來,然後用這些質數爆搜出合法的數的數量。

樸素的\(\text{dfs}\)還是太慢了,需要加一些剪枝。

  • 比如說,如果【當前乘積】乘以【當前考慮的質數】,已經\(>n\)了,則後面更大的質數一定不會選上,所以可以直接累加答案並返回(不需要往後走到最後一個質數再返回)。
  • 再比如說,如果【當前乘積】乘以【當前考慮的質數的平方】,已經\(>n\)了,則後面的質數裡,至多隻會再選\(1\)個。我們二分出可以選的最大質數,然後把方案數累加到答案裡,並直接返回。

加上這兩個剪枝後,爆搜就跑的非常快了,足以通過\(k\leq \sqrt{n}\)的情況。


參考程式碼:

//problem:HDU6834(1008)
#include <bits/stdc++.h>
using namespace std;

#define pb push_back
#define mk make_pair
#define lob lower_bound
#define upb upper_bound
#define fi first
#define se second
#define SZ(x) ((int)(x).size())

typedef unsigned int uint;
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int,int> pii;

template<typename T>inline void ckmax(T& x,T y){x=(y>x?y:x);}
template<typename T>inline void ckmin(T& x,T y){x=(y<x?y:x);}

const int MAXN=31623;//sqrt(MAXN)
int n,K;
int p[MAXN+5],cnt_p;
bool v[MAXN+5];
void sieve(int lim){
	for(int i=2;i<=lim;++i){
		if(!v[i]){
			p[++cnt_p]=i;
		}
		for(int j=1;j<=cnt_p && i*p[j]<=lim;++j){
			v[i*p[j]]=1;
			if(i%p[j]==0){
				break;
			}
		}
	}
}
int ans,lim;
void dfs(int idx,int prod){
	if(idx==lim){
		ans++;
		return;
	}
	if((ll)prod*p[idx]>n){
		ans++;
		return;
	}
	if((ll)prod*p[idx]*p[idx]>n){
		int l=idx,r=lim;
		while(r-l>1){
			int mid=(l+r)/2;
			if((ll)prod*p[mid]<=n)l=mid;
			else r=mid;
		}
		ans+=l-idx+2;
		return;
	}
	for(ll x=1;;x*=p[idx]){
		if(x*prod > n)break;
		dfs(idx+1,x*prod);
	}
}

struct Min25{
	int n,sqrt_n;
	int val[MAXN*2+5],id1[MAXN+5],id2[MAXN+5],tot;
	int g[MAXN*2+5];
	inline int get_id(int w){
		if(w<=sqrt_n) return id1[w];
		else return id2[n/w];
	}
	void build(int _n){
		n=_n;
		tot=0;
		sqrt_n=sqrt(n);
		for(int i=1,j;i<=n;i=j+1){
			j=n/(n/i);
			int w=n/i;
			val[++tot]=w;
			if(w<=sqrt_n) id1[w]=tot;
			else id2[n/w]=tot;
			
			g[tot]=w-1;
		}
		for(int j=1;j<=cnt_p;++j){
			for(int i=1;i<=tot && (ll)p[j]*p[j]<=val[i];++i){
				int k=get_id(val[i]/p[j]);
				g[i]=g[i]-(g[k]-(j-1));
			}
		}
	}
	Min25(){}
}SN,SK;


void solve_case(){
	cin>>n>>K;
	if(K>=n){
		cout<<n<<endl;
		return;
	}
	if(K<=MAXN){
		ans=0;
		lim=cnt_p+1;
		for(int i=1;i<=cnt_p;++i){
			if(p[i]>K){
				lim=i;
				break;
			}
		}
		dfs(1,1);
		cout<<ans<<endl;
	}
	else{
		SN.build(n);
		SK.build(K);
		ans=0;
		int lst=SK.g[1];
		for(int i=K+1,j;i<=n;i=j+1){
			j=n/(n/i);
			int k=SN.get_id(j);
			assert(SN.val[k]==j);
			ans+=(SN.g[k]-lst)*(n/i);
			lst=SN.g[k];
		}
		cout<<n-ans<<endl;
	}
}
int main() {
	sieve(MAXN);
	int T;cin>>T;while(T--){
		solve_case();
	}
	return 0;
}