【洛谷5438】【XR-2】记忆(数论+容斥)
- 有一个由\(l\sim r\)构成的排列,定义其权值为相邻两数乘积为完全平方数的数量。
- 求可能的最大权值。
- \(l\le r\le 10^{14}\)
显然的最优策略
一个众所周知的结论,两数相乘能得到一个完全平方数,当且仅当两数除去各自的平方因子后相等。
那么对于这道题,显然最优的策略就是把除去平方因子后相等的数全部堆在一起,然后考虑计算贡献。
\(l=1\)时的解法
先假设\(l=1\),那么假设\([1,r]\)中除去平方因子后等于\(x\)的数有\(x,2^2x,3^2x,...,k^2x\),则把它们全部堆在一起能产生的贡献就是\(k-1\)。
想到把贡献分配掉,\(k-1\)的贡献分给\(k\)个数必然会导致有一个数分不到,那不如我们就让最特殊的\(x\)没有贡献好了。
因此,此时的答案就是\([1,r]\)中平方因子不为\(1\)的数的个数。
平方因子为\(1\)的数的个数
这令我们联想到一道题目:【BZOJ2440】[中山市选2011] 完全平方数。这道题要求的是\([1,n]\)中平方因子为\(1\)的数的个数,显然用\(n\)减去便是平方因子不为\(1\)的数的个数。
而那道题的做法是利用莫比乌斯函数容斥,就是用含偶数个质数的平方因子的数的个数减去含奇数个质数的平方因子的数的个数,得出计算式:
其实\(\lfloor\frac n{i^2}\rfloor\)也是可以除法分块的,就是每次让\(r=\sqrt{\frac n{\lfloor\frac n{l^2}\rfloor}}\)。
但这样毕竟还是太慢(之后的做法中还需要多次调用这个函数),因此我们还需要考虑一种在上题中被放弃掉的办法。
一个数平方因子不为\(1\)等价于\(\mu(n)=0\),为\(1\)等价于\(\mu(n)=\pm1\Leftrightarrow\mu(n)^2=1\),因此得出另一种计算式:
这个式子直接计算的复杂度更高了,但它好就好在它的计算式中与\(n\)无关,可以预处理前缀和。
因此我们最终得出一个比较优秀的方法,就是\(n\)小的时候直接返回预处理出的前缀和,否则再跑上面的除法分块。
\(l\not=1\)时的解法
\(l\not=1\)时,首先仍然套用上面的做法,求出\([l,r]\)中平方因子不为\(1\)的数的个数。
然后就发现,这个算法的问题在于,如果某个平方因子为\(1\)的数\(x\)不在区间内,之前我们没有分给它的贡献在这里是本来就不存在的,还需要额外减去。
也就是说,对于\([1,l-1]\)的所有平方因子为\(1\)的数\(x\),如果存在\(k\)满足\(l\le k^2x\le r\),需要给答案减去一点贡献。
于是去枚举\(k\),移项变成\(\lfloor\frac{l-1}{k^2}\rfloor<x\le\lfloor\frac r{k^2}\rfloor\)。
要求出这个区间内平方因子为\(1\)的数的个数,直接调用先前提到的函数\(Calc()\)即可。
注意一个\(x\)可能存在多个\(k\)能使它满足条件,但只应计算一次,不过由于\(k\)增大时询问区间的左右端点必然同时递减,只要让每次的右端点和上次的左端点取个\(min\)即可。
代码:\(O(\)分析不来\()\)
#include<bits/stdc++.h>
#define Tp template<typename Ty>
#define Ts template<typename Ty,typename... Ar>
#define Reg register
#define RI Reg int
#define Con const
#define CI Con int&
#define I inline
#define W while
#define SN 10000000
#define LL long long
using namespace std;
LL L,R;
int Pt,P[SN+5],mu[SN+5],s[SN+5],s2[SN+5];I void Sieve()//线性筛预处理
{
mu[1]=1;for(RI i=2,j;i<=SN;++i) for(!P[i]&&(mu[P[++Pt]=i]=-1),//筛μ
j=1;j<=Pt&&i*P[j]<=SN;++j) if(P[i*P[j]]=1,i%P[j]) mu[i*P[j]]=-mu[i];else break;
for(RI i=1;i<=SN;++i) s[i]=s[i-1]+mu[i],s2[i]=s2[i-1]+mu[i]*mu[i];//求μ的前缀和及μ2的前缀和
}
I LL Calc(Con LL& x)//求[1,x]中平方因子为1的数的个数
{
if(x<=SN) return s2[x];LL t=0;//x较小时,返回预处理出的前缀和
for(LL l=1,r,m=sqrt(x);l<=m;l=r+1) r=sqrt(x/(x/l/l)),t+=x/l/l*(s[r]-s[l-1]);return t;//莫比乌斯容斥+除法分块
}
int main()
{
Sieve(),scanf("%lld%lld",&L,&R);LL x,y,lst=L-1,ans=R-L+1-(Calc(R)-Calc(L-1));//初始答案
for(RI i=2;1LL*i*i<=R;++i) x=(L-1)/i/i,y=min(R/i/i,lst),x<y&&(ans-=Calc(y)-Calc(x)),lst=x;//枚举平方因子i^2减去非法贡献
return printf("%lld\n",ans),0;
}