ACM竞赛数学学习笔记
欧拉筛的证明
之前一直都是贴板子。今天花了一点时间看了下证明,原来是如此简洁优雅。欧拉大神orz
一。正确性。 对于任何一个大于合数C,设p为C的最小质因子,令A=C/p,那么显然有A的最小质因子大于等于p,于是在进行到A枚举已存储的质数时,枚举到A的最小质因子之前p一定被枚举到了,于是C一定被筛出了。这就证明了所有的合数都会被筛出,也就证明了欧拉筛的正确性。
二。时间的线性。 只需要证明任何一个合数都不会被重复筛。 观察欧拉筛的过程发现所有的合数都是以一个质数乘另一个数的形式被筛出的。那么设C=A·p1,p1是C的最小质因子,假设存在另一组数满足C=B·p2,则有p2大于等于p1,而B的质因子必然包含p1,那么当进行到B的时候,在枚举到p1的时候就已经退出循环,于是C不会被重复筛出。即证明了时间复杂度。
洛谷p2522(莫比乌斯反演,数论分块)
这题要求: \(\sum_{i=a}^{b}\sum_{j=c}^{d}[gcd(i,j)=k]\)
可以容斥一下变成四个形为\(\sum_{i=1}^{x}\sum_{j=1}^{y}[gcd(i,j)=k]\)的和差。
对于这个式子:
1)oi-wiki上的解法(硬算,对单位函数\(\epsilon(n)\)卷积展开):
一个重要结论:\(\sum_{d|n}\mu(d)=\epsilon(n)\)
\(f(x,y)=\sum_{i=1}^{x}\sum_{j=1}^{y}[gcd(i,j)=k]\)
\(=\sum_{i=1}^{\frac{x}{k}}\sum_{j=1}^{\frac{y}{k}}[gcd(i,j)=1]\)
\(=\sum_{i=1}^{\frac{x}{k}}\sum_{j=1}^{\frac{y}{k}}\epsilon(gcd(i,j))\)
\(=\sum_{i=1}^{\frac{x}{k}}\sum_{j=1}^{\frac{y}{k}}\sum_{d|gcd(i,j)}\mu(d)\)
\(=\sum_{i=1}^{\frac{x}{k}}\sum_{j=1}^{\frac{y}{k}}\sum_{d|gcd(i,j)}\mu(d)\)
\(=\sum_{d=1}^{\frac{x}{k}}\mu(d)\sum_{i=1}^{\frac{x}{k}}[d|i]\sum_{j=1}^{\frac{y}{k}}[d|j]\)
\(=\sum_{d=1}^{\frac{x}{k}}\mu(d)\frac{x}{kd}\frac{y}{kd}\)
预处理\(\mu(n)\)及其前缀和后可通过数论分块在\(o(\sqrt{n})\)时间求出
2)某大牛莫比乌斯反演解法(构造一个\(F(n)\)十分直观。
参见博客:大佬博客
AC代码:
#include <bits/stdc++.h>
using namespace std;
const int maxn=50005;
int Mo[maxn],prime[maxn],P[maxn];
int sumMo[maxn];
void getMo(){
int cnt=0;
for(int i=1;i<maxn;i++) prime[i]=1;
Mo[1]=1; prime[1]=0;
for(int i=2;i<maxn;i++){
if(prime[i]){
P[++cnt]=i; Mo[i]=-1;
}
for(int j=1;j<=cnt&&i*P[j]<maxn;j++){
prime[i*P[j]]=0;
if(i%P[j]==0){
Mo[i*P[j]]=0;
break;
}
Mo[i*P[j]]=-Mo[i];
}
}
for(int i=1;i<maxn;i++) sumMo[i]=sumMo[i-1]+Mo[i];
}
long long getans(int x,int y,int k){
if(x>y) swap(x,y);
int n1=x/k,n2=y/k;
long long ans=0;
int i=1,j=1;
while(i<=n1&&j<=n2){
int ni=n1/(n1/i),nj=n2/(n2/j);
int tmp=min(ni,nj);
ans += 1ll*(n1/i)*(n2/j)*(sumMo[tmp]-sumMo[i-1]);
i=j=tmp+1;
}
return ans;
}
int main(){
int t;
getMo();
cin >> t;
while(t--){
int a,b,c,d,k;
cin >> a >> b >> c >> d >> k;
cout << getans(b,d,k)-getans(a-1,d,k)-getans(b,c-1,k)+getans(a-1,c-1,k) << endl;
}
return 0;
}
hdu6588 function(一个重要结论)
题目要求 \(\sum_{i=1}^{n}gcd(\sqrt[3]{i},i)\)
首先数论分块很容易有原式=\(\sum_{x=(\sqrt[3]{n})^3}^{n}gcd(\sqrt[3]{n},x)+\sum_{x=1}^{\sqrt[3]{n}}\sum_{i=x^3}^{(x+1)^3-1}gcd(x,i)\)
令\(\sqrt[3]{n}=r\)
此处有一个重要公式:\(\sum_{i=1}^{n}gcd(s,i)=\sum_{d|s}\frac{n}{d}\phi(d)\), \(\phi\)为欧拉函数
于是\(\sum_{x=1}^{r}\sum_{i=x^3}^{(x+1)^3-1}gcd(x,i)\)
=\(\sum_{x=1}^{r}\sum_{d|x}(\frac{(x+1)^3-1}{d}-\frac{x^3-1}{d})\phi(d)\)
=\(\sum_{d=1}^{r}\phi(d)\sum_{i=1}^{\frac{r}{d}}(\frac{(di+1)^3-1}{d}-\frac{di^3-1}{d})\)
=\(\sum_{d=1}^{r}\phi(d)\sum_{i=1}^{\frac{r}{d}}(3di^2+3i+1)\)
可以在\(O(\sqrt[3]{n})\)的时间求出答案。
AC代码:
#include <bits/stdc++.h>
using namespace std;
typedef __int128 ll;
typedef long long lll;
inline __int128 read(){
__int128 x=0,f=1;
char ch=getchar();
while(ch<'0'||ch>'9'){
if(ch=='-')
f=-1;
ch=getchar();
}
while(ch>='0'&&ch<='9'){
x=x*10+ch-'0';
ch=getchar();
}
return x*f;
}
inline void print(__int128 x){
if(x<0){
putchar('-');
x=-x;
}
if(x>9)
print(x/10);
putchar(x%10+'0');
}
const int N=1e7+5;
int phi[N], p[N], cnt, vis[N];
void getphi() {
phi[1] = 1;
for (int i=2; i<N; ++i) {
if (!vis[i]) p[++cnt]=i,phi[i]=i-1;
for (int j=1,t; j<=cnt&&i*p[j]<N; ++j) {
vis[t=i*p[j]] = 1;
if (i%p[j]==0) {phi[t]=phi[i]*p[j];break;}
phi[t]=phi[i]*phi[p[j]];
}
}
}
int main(){
int T;
scanf("%d",&T);
getphi();
while(T--){
ll n;
n=read();
int mod=998244353;
lll ans=0;
lll r=0;
while(ll(r)*r*r-1<=n) r++;
r-=2;
for(int d=1;d<=r;d++){
lll tmp=r/d;
lll pp;
pp=tmp;
pp=(pp+3*(tmp*(tmp+1)/2)%mod)%mod;
pp=(pp+d*((tmp*(tmp+1)/2%mod)*(2*tmp+1)%mod)%mod)%mod;
pp=pp*phi[d]%mod;
ans=(ans+pp)%mod;
}
r++;
ll tt=ll(r)*r*r-1;
int rr=sqrt(r+0.5);
for(int d=1;d<=rr;d++){
if(r%d==0){
ans=(ans+phi[d]*(n/d-tt/d))%mod;
int tm=r/d;
if(tm!=d){
ans=(ans+phi[tm]*(n/tm-tt/tm))%mod;
}
}
}
printf("%lld",ans);
printf("\n");
}
}