1 条题解
-
0
自动搬运
来自洛谷,原作者为

紫钦
永远没有新的开始,因为我们从未停止。搬运于
2025-08-24 21:58:56,当前版本为作者最后更新于2019-07-31 19:21:42,作者可能在搬运后再次修改,您可在原文处查看最新版自动搬运只会搬运当前题目点赞数最高的题解,您可前往洛谷题解查看更多
以下是正文
题目链接:P4318 完全平方数 。
题目大意: 组数据,每组数据给出一个正整数 ,求第 个不含大于 的完全平方数因子的正整数。
数据规模: 。
解法一:杜教筛+二分
这个平方因子的问题很容易让我们想到莫比乌斯函数的性质。所以,若设答案为 ,则有: 且 ,
考虑到 函数只有 和 三种取值,故可以使用 函数来回避 时不好通过求前缀和来计数的情况,于是 且 。
题目给定 ,求解 ,又考虑到 的前缀和必然单调不降,所以采用二分来找到 。二分找到的满足 的前缀和大于等于 的最小的 即为答案。
不过二分的范围题目没给,所以拿来题解跑了一下。发现第 个不含平方因子的数是 。为了表现出我们并没有看题解的样子,所以我们将二分的上限设定为 。当然设定为 也可以。
其实这个数值我们可以大概计算出来,暴力算一下可计算范围内 的值,就可以大概推断出 时的答案不超过 了。
当然更加可行的做法是:写好代码后,手动增加上限,并一次又一次跑 的数据,当某次上限设定大于 时,就得到这个 的答案了,此时我们就可以让这个数做二分上限了。
二分下限就定为 好了,显然答案是大于等于 的。
那么二分势必带来 的复杂度,这个复杂度远远达不到题目的上限。于是题目另一部分复杂度应该存在于计算 上。
设 。
数据范围达到了 ,而要求前缀和的 函数显然是个积性函数,所以考虑杜教筛。
那么我们就需要一个很好求前缀和的函数 ,使得 的前缀和也好求。
这个函数可相当地不好找。。。
考虑 ,它的前缀和并不好求。但是这种真值表达式形式的函数,拥有其特殊性:可以通过改变枚举方式,使真值表达式恒成立,从而使该函数在枚举时变为常函数。
再来考虑 $(f\times g)(n)=\sum\limits_{d|n}g(d)f({\large\frac nd})$ ,通过观察我们发现, 仅在 取到 的最大平方因子时值为 ,否则值为 。
简要证明:若 不为 的最大平方因子,则 必然含有平方因子,则 ;若 为 的最大平方因子,则 必然不含有平方因子,则 ;若 不为完全平方数,则 。
因为 一定是 的一个平方因子,所以 的最大平方因子肯定存在。
于是 ,那么 的前缀和很好求。
使用杜教筛的套路,得:
$g(1)S(n)=\sum\limits_{i=1}^n(f\times g)(i)-\sum\limits_{i=2}^ng(i)S({\large\lfloor\frac ni\rfloor})$ ,
即:$S(n)=n-\sum\limits_{i=2}^ng(i)S({\large\lfloor\frac ni\rfloor})$ ,
然后直接改变枚举方式,枚举平方数,得:
$S(n)=n-\sum\limits_{i=2}^ng(i)S({\large\lfloor\frac ni\rfloor})=n-\sum\limits_{i=2}^{\sqrt n}S({\large\lfloor\frac n{i^2}\rfloor})$ ,
这个神一般的杜教筛操作真令人眼花缭乱。好了,现在 前缀和直接回避了,用不着求了。
注意事项
-
时间复杂度为 ,个人感觉这个复杂度很危险,过不去的样子。
-
注意到杜教筛后面那一项只有 项,整除分块优化不明显,且除法常数很大,所以别整除分块。
所以别用整除分块。
-
注意 ,二分时区间左右端点要开成 ,否则相加计算 时爆 。其他地方不用开。我开了很多没必要的 然后 了,见 记录 。
代码
#include<cstdio> #include<cstdlib> #include<unordered_map> using namespace std; namespace Qza { int T,K,mu2[1000002],prime[78499]; // prime[78498]=999983; bool vis[1000002]; unordered_map<int,int> map_S; inline void init(int x) { static int cnt=0; long long k; mu2[1]=1; for(register long long i=2ll;i<=x;++i) { if(!vis[i]) prime[++cnt]=i,mu2[i]=1; for(register int j=1;j<=cnt && (k=i*prime[j])<=x;++j) { vis[k]=true; if(i%prime[j]) mu2[k]=mu2[i]; else break; } mu2[i]+=mu2[i-1]; } } inline int S(int x) { if(x<=1000000) return mu2[x]; if(map_S[x]) return map_S[x]; int res=0; for(register int l=2;l*l<=x;++l) { res+=S(x/(l*l)); } return map_S[x]=x-res; } int read() { char ch=getchar(); int x=0; while(ch<'0' || ch>'9') ch=getchar(); while(ch>='0'&&ch<='9') x=(x*10)+(ch^48), ch=getchar(); return x; } void main() { T=read(); if(!T) return ; // 杞人忧天,但是题目确实没保证 T>0。。。 init(1000000); register long long l,r; register int mid; do { K=read(); l=K; r=1644934082ll; while(l<r) { mid=(l+r)>>1; if(S(mid)>=K) r=mid; else l=mid+1; } printf("%lld\n",r); }while(--T); return ; } } int main() { Qza::main(); return 0; }解法二:容斥+二分
二分答案是没办法的回避的。这种求出序列第几个数是几的,经验告诉我们,
平衡树二分答案应该是不错的选择(且本题的单调性很明显)。那么,为什么想到了容斥?
有了二分答案的步骤,我们就需要考虑如何快速求出 内不含非 完全平方数因子的数的个数。
我们很自然地想到,可以通过筛去完全平方数的倍数,而留下不含平方因子的数。在剩余的数上操作,要方便得多。
可惜线性筛在 的数据规模下吐血而亡,所以我们不能靠筛,我们需要直接计算。
直接计算的话,我们知道, 区间内, 的倍数共有 个。进一步思考发现,我们当然不会将 的倍数再筛选去掉(再去掉就减多了)。所以我们只需要去掉所有质数的完全平方数的倍数。
嗯,这就想到容斥了。因为筛重复了。比如说 的倍数和 的倍数都去掉后, 的倍数就减多了,需要加回来。
由此我们想到,可以枚举 区间内的所有质数,然后容斥。这样,我们就需要先减去 的倍数的个数,再加上 的倍数的个数,再减去 。
显然工作量极大。而且枚举的顺序很难确定。
不过,我们发觉,容斥时,如果若干个相异质数(也可以为一个质数)的积大于 的话(设这个积为 ),那么 。这就是容斥的边界。
这告诉我们,枚举的次数不超过 次。再加上枚举的数需为若干相异质数的乘积的限制条件,我们会自然想到莫比乌斯函数 。
莫比乌斯函数具有很好的容斥天赋。若像解法一那样,将莫比乌斯函数做个平方来计数,属实是有些屈才了。
故我们得到 中不含非 完全平方数因子的数的个数为 $\sum\limits_{i=1}^{\lfloor\sqrt n\rfloor}\mu(i){\large\lfloor\frac n{i^2}\rfloor}$ ,式中 的存在保证了 一定为若干相异质数的积,且恰好按照质因子个数来分配系数( 和 )。
至此,容斥思路已成型。
注意事项
-
线性筛求 时,考虑到 ,故筛出 项足矣。
万万不能由 而只筛 项!
-
二分答案仍然要开 ,除此之外,无需 。
-
同样不需要整除分块。
-
时间复杂度为 ,比杜教筛快了很多。
代码
评测记录 。
#include<cstdio> #include<cstdlib> using namespace std; namespace Qza{ int T,K,prime[4253],mu[40560]; // prime[4252]=40559; bool vis[40560]; void init(int x) { static int cnt=0; int k; mu[1]=1; for(int i=2;i<=x;++i) { if(!vis[i]) prime[++cnt]=i, mu[i]=-1; for(int j=1;j<=cnt && (k=i*prime[j])<=x;++j) { vis[k]=true; if(i%prime[j]) mu[k]=-mu[i]; else break; } } } bool judge(int x) { int ans=0;int i; for(i=1;i*i<=x;++i) { ans+=mu[i]*(x/(i*i)); } return ans>=K; } int read() { char ch=getchar(); int x=0; while(ch<'0' || ch>'9') ch=getchar(); while(ch>='0'&&ch<='9') x=(x*10)+(ch^48), ch=getchar(); return x; } void main() { T=read(); if(!T) return ; init(40559); long long l,r; int mid; do { K=read(); l=K; r=1644934082ll; while(l<r) { mid=(l+r)>>1; if(judge(mid)) r=mid; else l=mid+1; } printf("%lld\n",r); }while(--T); return ; } } int main() { Qza::main(); return 0; } -
- 1
信息
- ID
- 3273
- 时间
- 1000ms
- 内存
- 125MiB
- 难度
- 6
- 标签
- 递交数
- 0
- 已通过
- 0
- 上传者