题目大意
给定\(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;
}