题目大意
给你一个数
n(n≤1018)
,求第
n
个含大于
比赛时卡常卡到头秃
发现一个数
x
含有平方因子等价于
令
si
表示
μ2
的前缀和,那么答案要满足的条件就是
ans−sans=n
二分答案,然后问题就是怎么求
si
。
有一个公式:
μ2n=∑d2|nμd
那么
sn=∑i=1nμ2n=∑i=1n∑d2|nμd=∑d=1n√μd⌊nd2⌋
这样 μ 的前缀和可以用杜教筛求, sn 用分块求出。
可以证明复杂度为 O(n13log2n) 。
但这样还不能通过此题。
有一个结论:答案的一个接近值是 n1−6π2 ( 证明戳这里)。
所以直接在接近值周围一个范围内二分即可。
时间复杂度 O(n13lognloglogn) 。
#include<map>
#include<cmath>
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
const int N=60000010;
const double pi=acos(-1.0);
typedef long long ll;
map<int,int>M;
ll n,l,r,Mid,Ans;
int mu[N],mu2[N],p[N>>3],num;
bool b[N];
inline void Init(){
mu[1]=mu2[1]=1;
for(int i=2;i<N;i++){
if(!b[i]){
p[++num]=i;mu[i]=-1;
}
int t;
for(int j=1;j<=num&&(t=p[j]*i)<N;j++){
b[t]=1;
if(!(i%p[j])){
mu[t]=0;
break;
}
mu[t]=-mu[i];
}
}
for(int i=2;i<N;i++)mu2[i]=mu2[i-1]+(!mu[i]?0:1),mu[i]+=mu[i-1];
}
int Solve(int n){
if(n<N)return mu[n];
if(M.count(n))return M[n];
int Ans=1;
for(int i=2;i<=n;){
int k=n/i,j=n/k;
Ans-=Solve(k)*(j-i+1);
i=j+1;
}
return M[n]=Ans;
}
inline ll Get(ll n){
if(n<N)return mu2[n];
ll Ans=0,lst=0,tmp,t,i=1;
for(;i*i*i<=n;i++)Ans+=(n/(i*i))*((tmp=mu[i])-lst),lst=tmp;
for(Ans-=(t=n/(i*i))*lst;t;t--)Ans+=Solve(sqrt(n/t));
return Ans;
}
int main(){
scanf("%lld",&n);
Init();
l=n/(1-6/pi/pi),r=l+400000,l-=400000;
l=max(l,1ll);
while(l<=r){
Mid=l+r>>1;
if(Mid-Get(Mid)>=n)Ans=Mid,r=Mid-1;else l=Mid+1;
}
cout<<Ans<<endl;
return 0;
}