数学计数——基础筛法

题单介绍

[参考资料:筛法(OI-wiki)](https://oi-wiki.org/math/number-theory/sieve/) ### Chapter 1. 知识点 注:以下整数均指 $>1$ 的整数。 首先声明之后代码里的主要变量含义:$\texttt{cnt}$ 表示素数个数;$\texttt{p}$ 为记录所有素数的数组;$\texttt{vis}$ 表示每个数是否是素数。 1. 普通筛法 - 思想:整数的整数倍数是合数; - 时间复杂度:$O(n\log n)$。 ~~~cpp for(int i=2;i<=n;i++) { if(!vis[i]) p[++cnt]=i; for(int j=2*i;j<=n;j+=i) //整数的整数倍数是合数 vis[j]=1; } ~~~ 2. 埃氏筛 - 素数的整数倍数是合数; - 时间复杂度:$O(n\log \log n)$。 ~~~cpp for(int i=2;i<=n;i++) if(!vis[i]) { p[++cnt]=i; for(int j=2*i;j<=n;j+=i) //素数的整数倍数是合数 vis[j]=1; } ~~~ 3. 线性筛 - 思想:整数的素数倍数是合数; - 时间复杂度:$O(n)$。 - 时间复杂度的证明:枚举整数的素数倍数,当枚举到的素数是当前整数的约数时停止。设 $n$ 的最小质因子是 $x_n$,$n$ 会且仅会在 $i=\frac{n}{x_n}$ 的 $x_n$ 倍时倍筛到。所以每个数仅仅会被筛到一次,时间复杂度是线性的。 ~~~cpp for(int i=2;i<=n;i++) { if(!vis[i]) p[++cnt]=i; for(int j=1;j<=cnt&&p[j]*i<=n;j++) //整数的素数倍数是合数 { vis[p[j]*i]=1; if(i%p[j]==0) break; } } ~~~ 4. 区间筛 - 此算法为埃氏筛思想的一种延申运用。 - 问题描述:求区间 $[a,b]$ 内所有的素数。$a\le b\le 10^{12},b-a\le 10^6$。 - 算法流程:1. 线性筛出 $1\sim \sqrt b$ 内的所有素数;2. 运用埃氏筛的思想筛掉 $[a,b]$ 中的所有合数。 ~~~cpp init(ceil(sqrt(r))); //线性筛 //筛去 [a,b]中的合数(埃氏筛) for(int i=1;i<=cnt;i++) //枚举每一个在 [1,sqrt(b)]内的素数 for(int j=(a-1)/pri[i]+1;1ll*j*pri[i]<=b;j++) ss[1ll*j*pri[i]-a]=1; //ss[i]=1/0: a+i是否为合数 int ans=0; for(int i=0;i<=r-l;i++) //注意循环上下界 if(!ss[i]) ans++; ~~~ 5. 线性筛求积性函数* - **后续会继续补充该方面的知识。** - 积性函数的定义:考虑一个定义在正整数上的函数 $f$,若 $\forall a,b\in \mathbb{N}^+,\gcd(a,b)=1$,都有 $f(a)\times f(b)=f(ab)$,则称 $f$ 为积性函数。 - 常见的两个积性函数: - 欧拉函数 $\varphi$:$\varphi(n)=\sum_{k=1}^n [\gcd(i,n)=1]$。 - 莫比乌斯函数 $\mu$:当 $n$ 有平方因子时,$\mu(n)=0$;否则 $\mu(n)=(-1)^k$,$k$ 为 $n$ 的素因子个数。 - 可以用线性筛计算欧拉函数的原因;对于每个合数 $n$,在线性筛的过程中都会拆成 $\frac{n}{x_n}$ 和 $x_n$。设 $\gcd(x_n^∞,n)=x_n^k$,则 $\gcd(\frac{n}{x_n^k},x_n^k)=1$。 - 算法流程:1. 直接计算出 $f(x_n^k)$;2. 对于其他合数 $n$ ,$f(n)=f(\frac{n}{x_n^k})\times f(x_n^k)$。 - 下面给出以计算欧拉函数为例的核心代码($\texttt{phi}$ 表示欧拉函数值): ~~~cpp for(int i=2;i<=n;i++) { if(!vis[i]) { p[++cnt]=i; phi[i]=i-1; //由于欧拉函数的特性,只需特殊计算 phi(p) 即可 } for(int j=1;j<=t&&p[j]*i<=n;j++) //整数的素数倍数是合数 { vis[p[j]*i]=1; if(i%p[j]) phi[p[j]*i]=phi[i]*(p[j]-1); else { phi[p[j]*i]=phi[i]*p[j]; break;} } } ~~~ ### Chapter 2. 题目 1. [P3383 【模板】线性筛素数](https://www.luogu.com.cn/problem/P3383) 2. [P3912 素数个数](https://www.luogu.com.cn/problem/P3912) - 两道线性筛模板题。 3. [P1835 素数密度](https://www.luogu.com.cn/problem/P1835) - 区间筛模板题。 4. [P1865 A % B Problem](https://www.luogu.com.cn/problem/P1865) - 线性筛模板 $+$ 前缀和。 第 $1\sim 4$ 题都是比较简单的模板题,之后的题将会有些难度,很多是涉及到欧拉函数的题。 5. [P2568 GCD](https://www.luogu.com.cn/problem/P2568) - 若 $\gcd(x,y)=p$( $p$ 为素数,$x\le y$),则 $\gcd(\frac{x}{p},\frac{y}{p})=1$。 - 我们对每个素数 $p$ 考虑满足 $1\le x'\le y'\le \frac{n}{p},\gcd(x',y')=1$ 的正整数对 $(x',y')$ 的个数。 - 那么先考虑对 $1\le y\le p$ 有多少个 $1\le x\le y$ 满足 $\gcd(x,y)=1$。可以发现这就是欧拉函数的定义。 - 那么我们只需要通过线性筛预处理欧拉函数,然后求出欧拉函数的前缀和,最后再对每个素数算出结果并相加得到答案即可。 ~~~cpp init(); //线性筛 + 预处理欧拉函数 for(int i=1;i<=n;i++) sum[i]=sum[i-1]+phi[i]; //欧拉函数前缀和 for(int i=1;i<=cnt&&pri[i]<=n;i++) ans+=2*sum[n/pri[i]]-1; //求和 ~~~ 6. [P2303 [SDOI2012] Longge 的问题](https://www.luogu.com.cn/problem/P2303) - $\sum\limits_{i=1}^n \gcd(i, n)=\sum\limits_{k=1}^n[n\bmod k=0]k\times\varphi(\frac{n}{k})$。 - 时间复杂度:$O(\text{因子个数}\times\sqrt n)$,直接计算可过。 ~~~cpp long long phi(long long x) { long long res=x; for(long long i=2;i*i<=x;i++) if(x%i==0) //i为 x的素因数 { res=res/i*(i-1); while(x%i==0) x/=i; } if(x>1) res=res/x*(x-1); return res; } long long getans(long long n) { long long ans=0,i=1; while(i*i<n) { if(n%i==0) ans+=(i+(n/i))*getphi(i); //同时计算 i和 x/i的贡献 i++; } if(i*i==x) ans+=i*getphi(i); //sqrt(x)和 x/sqrt(x) return ans; } ~~~ 本题还有更好的做法:[link](https://www.luogu.com.cn/blog/PinkRabbit/solution-p2303)。 7. [P2158 [SDOI2008] 仪仗队](https://www.luogu.com.cn/problem/P2158) - 设左下角坐标为 $(0,0)$,则点能被看到的点的坐标 $(x,y)$ 满足 $\gcd(x,y)=1$。 - 所以答案就是 $2(\varphi(1)+\cdots+\varphi(n))+1$。 8. [P2398 GCD SUM](https://www.luogu.com.cn/problem/P2398) - 考虑对每个 $d=1,2,\cdots,n$ 计算满足 $\gcd(i,j)=d$ 的 $(i,j)$ 有多少个,即 $\gcd(x,y)=1,x,y\le \frac{n}{d}$,由第七题就可以知道答案。 9. [P1390 公约数的和](https://www.luogu.com.cn/problem/P1390) - 与第八题一样的。 10. [P1447 [NOI2010] 能量采集](https://www.luogu.com.cn/problem/P1447) - 就是要求 $\sum_{i=1}^n\sum_{j=1}^m\gcd(i,j)$ 的值。 ```cpp const int MAXN=1e5+10; int n,m; long long num[MAXN],ans; int main() { scanf("%d%d",&n,&m); if(n>m) swap(n,m); for(int i=n;i;i--) //注意循环的顺序 { num[i]=1ll*(n/i)*(m/i); for(int j=2*i;j<=n;j+=i) num[i]-=num[j]; ans+=(2*i-1)*num[i]; //计算贡献 } printf("%lld",ans); return 0; } ```

题目列表

  • 【模板】线性筛素数
  • 素数个数
  • 素数密度
  • A % B Problem
  • GCD
  • [SDOI2012] Longge 的问题
  • [SDOI2008] 仪仗队
  • GCD SUM
  • 公约数的和
  • [NOI2010] 能量采集