筛法学习笔记

文章目录

  • 埃式筛法
  • 线性筛
  • 杜教筛
    • 模板题
    • luoguP3768
    • 51nod 1237
    • 51nod 1239
    • 51nod 1220
    • [BZOJ #3512. DZY Loves Math IV](https://darkbzoj.tk/problem/3512)
    • [BZOJ #3930. [CQOI2015]选数](https://darkbzoj.tk/problem/3930)

埃式筛法

for(int i=2;i<=n;i++) if(!v[i])
	for(int j=i*i;j<=n;j+=i) v[j]=1;

复杂度: O ( n log ⁡ log ⁡ n ) O(n\log\log n) O(nloglogn).

线性筛

for(int i=2;i<=n;i++) {
	if(!v[i])prime[++tot]=i;
	for(int j=1;i*prime[j]<=n;j++) {
		v[i*prime[j]=1;
		if(i%prime[j]==0) break;
	}
}

复杂度 O ( n ) O(n) O(n).
原理:从大到小枚举质因数

杜教筛

参考博客

强烈建议学习之前做一些简单的莫比乌斯反演的题.(不用到杜教筛即可).
莫比乌斯反演题表

数论函数基础(前置知识):

  1. 简单数论函数: μ , φ , i d ( n ) = n , i d k ( n ) = n k , e ( n ) = [ n = 1 ] , σ k ( 约 数 的 k 次 方 和 ) \mu,\varphi,id(n)=n,id^k(n)=n^k,e(n)=[n=1],\sigma_k(约数的k次方和) μ,φ,id(n)=n,idk(n)=nk,e(n)=[n=1],σk(k)
  2. 积性函数: n ⊥ m n\bot m nm,则有 f ( n m ) = f ( n ) × f ( m ) f(nm)=f(n)\times f(m) f(nm)=f(n)×f(m),则为积性函数. 以上函数皆为积性函数.
  3. 一些性质. f , g 为 积 性 函 数 f,g为积性函数 f,g, h ( n ) = f ( n ) × g ( n ) h(n)=f(n)\times g(n) h(n)=f(n)×g(n)显然也是积性函数.
  4. 积性函数的运算: + , − , ∗ , +,-,*, +,,,求逆.
  5. 整除分块

复杂度: O ( n 2 3 O(n^{\frac{2}{3}} O(n32).
用途:求积性函数前缀和:
已知积性函数 f f f,求 S ( n ) = ∑ i = 1 n f ( i ) S(n)=\sum\limits_{i=1}^n f(i) S(n)=i=1nf(i).
处理技巧:
g g g为另一个积性函数
∑ i = 1 n ( f ∗ g ) ( i ) = ∑ i = 1 n g ( i ) ∗ S ( ⌊ n i ⌋ ) \sum\limits_{i=1}^n(f*g)(i)=\sum\limits_{i=1}^n g(i)*S(\lfloor\dfrac{n}{i}\rfloor) i=1n(fg)(i)=i=1ng(i)S(in)
则 有 g ( 1 ) ∗ S ( n ) = ∑ i = 1 n ( f ∗ g ) ( i ) − ∑ i = 2 n g ( i ) ∗ S ( ⌊ n i ⌋ ) ( 提 取 S ( n ) ) 则有g(1)*S(n)=\sum\limits_{i=1}^n(f*g)(i)-\sum\limits_{i=2}^n g(i)*S(\lfloor\dfrac{n}{i}\rfloor)(提取S(n)) g(1)S(n)=i=1n(fg)(i)i=2ng(i)S(in)(S(n))
∵ g 是 积 性 函 数 \because g是积性函数 g
∴ g ( 1 ) = 1 \therefore g(1)=1 g(1)=1
∴ S ( n ) = ∑ i = 1 n ( f ∗ g ) ( i ) − ∑ i = 2 n g ( i ) ∗ S ( ⌊ n i ⌋ ) \therefore S(n)=\sum\limits_{i=1}^n(f*g)(i)-\sum\limits_{i=2}^n g(i)*S(\lfloor\dfrac{n}{i}\rfloor) S(n)=i=1n(fg)(i)i=2ng(i)S(in)

杜教筛的重点在于找到方便的 g g g,使得 g , f ∗ g g,f*g g,fg都是简单的积性函数.

在1s内可以跑过 n = 1 0 10 ∼ 11 n=10^{10\sim 11} n=101011的数据.

复杂度证明:
有个性质:在求 S ( n ) S(n) S(n)时递归到的数比 S ( ⌊ n i ⌋ ) S(\lfloor\dfrac{n}{i}\rfloor) S(in)的要多.
所以要算前缀和的总共有 O ( n ) O(\sqrt n) O(n )个.
递归的求前缀和的 ⌊ n i ⌋ \lfloor\dfrac{n}{i}\rfloor in分别为(可能有相等的):

1 , 2 , 3 , 4 , . . . n , n n , n n − 1 . . . . . . , n 2 1,2,3,4,...\sqrt n,\dfrac{n}{\sqrt n},\dfrac{n}{\sqrt n-1}......,\dfrac{n}{2} 1,2,3,4,...n ,n n,n 1n......,2n

递归到 x x x的时候,前面的前缀和一定被计算过了.
可以发现,直接用 x \sqrt x x 的复杂度进行合并即可.

那么总复杂度为: O ( ∑ i = 1 n i + ∑ i = 2 n n i ) ≈ O ( ∑ i = 2 n n i ) = n 3 / 4 ( 定 积 分 估 计 ) O(\sum_{i=1}^{\sqrt n} \sqrt i+\sum_{i=2}^{\sqrt n}\sqrt{\dfrac{n}{i}})\approx O(\sum_{i=2}^{\sqrt n}\sqrt{\dfrac{n}{i}})=n^{3/4}(定积分估计) O(i=1n i +i=2n in )O(i=2n in )=n3/4().

我们发现对前面的一定范围的数,直接用线性筛可以均摊 O ( 1 ) O(1) O(1),可以不用根号复杂度,
所以我们设对前 n c ( c ∈ ( 0 , 1 ) ) n^c(c\in(0,1)) nc(c(0,1))直接预处理.

此时的复杂度为 O ( n c + ∑ i = 2 n 1 − c n i ) ≈ O ( n c + ∫ 0 n 1 − c n / x dx ⁡ ) = O ( n c + n 1 − c 2 ) O(n^c+\sum\limits_{i=2}^{n^{1-c}}\sqrt{\dfrac{n}{i}})\approx O(n^c+\int_0^{n^{1-c}}\sqrt{n/x}\operatorname{dx}) =O(n^c+n^{1-\frac{c}{2}}) O(nc+i=2n1cin )O(nc+0n1cn/x dx)=O(nc+n12c).

此时用一下均值不等式: c = 2 / 3 c=2/3 c=2/3.

Tips:答案记忆化才能保证复杂度.下面提供一种不用 m a p map map的简单做法.
贴个代码:

now为读入的值.
N=now^(2/3).
ll S(ll n) {
	if(n<N) return mu[n];//直接返回
	ll x=now/n,&ans=s[x];
	//x为存储的位置.  now/n实际上是找到原整除分块内的右端点作为标志. 这样x就在n^(1/3)内啦.
	if(vis[x]) return ans;
	/*vis[x]=1; ans=1;
	for(ll l=2,r;l<=n;l=r+1) {
		r=n/(n/l);
		(ans-=calc(l,r)*S(n/l)%mod) %= mod;
	}
	upd(ans);*/
	return ans;
}

模板题

  1. f = φ , g = 1 , f ∗ g = i d f=\varphi,g=1,f*g=id f=φ,g=1,fg=id.
  2. f = μ , g = i d , f ∗ g = φ f=\mu,g=id,f*g=\varphi f=μ,g=id,fg=φ

代码:

#include
#include
#include
#define ll long long
using namespace std;
const int M=1305,N=M*M;

ll now,s1[M],s2[M];
char v1[M],v2[M],cnt;

int prime[N],tot,mu[N];
ll phi[N];
void get(int x) {
	phi[1]=mu[1]=1;
	for(int i=2;i<=x;i++) {
		if(!phi[i]) phi[i]=i-1,mu[i]=-1,prime[++tot]=i;
		for(int j=1;i*prime[j]<=x;j++) 
			if(i%prime[j]) {
				phi[i*prime[j]]=phi[i]*(prime[j]-1);
				mu[i*prime[j]]=-mu[i];
			}
			else {
				phi[i*prime[j]]=phi[i]*prime[j];
				break;
			}
	}
	for(int i=2;i<=x;i++) 
		mu[i]+=mu[i-1],phi[i]+=phi[i-1];
}

ll S1(ll n) {
	if(n<N) return phi[n];
	ll x=now/n,&ans=s1[x];
	if(v1[x]==cnt) return ans;
	v1[x]=cnt; ans=(ll)n*(n+1)>>1;
	for(ll l=2,r;l<=n;l=r+1) {
		r=n/(n/l);
		ans-=(r-l+1)*S1(n/l);
	}
	return ans;
}

ll S2(ll n) {
	if(n<N) return mu[n];
	ll x=now/n,&ans=s2[x];
	if(v2[x]==cnt) return ans;
	v2[x]=cnt; ans=S1(n);
	for(ll l=2,r;l<=n;l=r+1) {
		r=n/(n/l);
		ans-=(l+r)*(r-l+1)/2*S2(n/l);
	}
	return ans;
}

int main() {
	get(N-1);
	int T; scanf("%d",&T);
	while(T--) {
		scanf("%lld",&now); ++cnt;
		printf("%lld %lld\n",S1(now),S2(now));
	}
	return 0;
}

练习:

luoguP3768

51nod 1237

提示: g c d ( i , j ) = ∑ k ∣ i , k ∣ j φ ( k ) gcd(i,j)=\sum_{k|i,k|j}\varphi(k) gcd(i,j)=ki,kjφ(k)
51nod 1238
∑ i = 1 n ∑ j = 1 n l c m ( i , j ) \sum_{i=1}^n\sum_{j=1}^n lcm(i,j) i=1nj=1nlcm(i,j)
= ∑ i = 1 n ∑ j = 1 n i ∗ j / d [ g c d ( i , j ) = d ] =\sum_{i=1}^n\sum_{j=1}^n i*j/d[gcd(i,j)=d] =i=1nj=1nij/d[gcd(i,j)=d]
= ∑ d = 1 n d ∗ ∑ i = 1 n / d ∑ j = 1 n / d i ∗ j [ g c d ( i , j ) = 1 ] =\sum_{d=1}^n d*\sum_{i=1}^{n/d} \sum_{j=1}^{n/d} i*j[gcd(i,j)=1] =d=1ndi=1n/dj=1n/dij[gcd(i,j)=1]
= ∑ d = 1 n d ∗ ∑ i = 1 n / d i ∗ ∑ j = 1 n / d j ∗ [ g c d ( i , j ) = 1 ] =\sum_{d=1}^n d*\sum_{i=1}^{n/d}i*\sum_{j=1}^{n/d} j*[gcd(i,j)=1] =d=1ndi=1n/dij=1n/dj[gcd(i,j)=1]
= ∑ d = 1 n d ∗ [ 2 ( ∑ i = 1 n / d i ∗ ∑ j = 1 i j ∗ [ g c d ( i , j ) = 1 ] ) − 1 ] =\sum_{d=1}^n d*[2(\sum_{i=1}^{n/d}i*\sum_{j=1}^{i} j*[gcd(i,j)=1]) -1] =d=1nd[2(i=1n/dij=1ij[gcd(i,j)=1])1]
= ∑ d = 1 n d ∗ [ 2 ( ∑ i = 1 n / d i ∗ i ∗ φ ( i ) + [ i = 1 ] 2 ) − 1 ] ( 由 更 相 减 损 法 得 j ⊥ i − > ( i − j ) ⊥ i , 所 以 互 质 的 数 成 对 出 现 ) =\sum_{d=1}^n d*[2(\sum_{i=1}^{n/d}i*\dfrac{i*\varphi(i)+[i=1]}{2}) -1] (由更相减损法得j\bot i->(i-j)\bot i,所以互质的数成对出现) =d=1nd[2(i=1n/di2iφ(i)+[i=1])1](ji>(ij)i,)
= ∑ d = 1 n d ∗ ∑ i = 1 n i 2 φ ( i ) =\sum_{d=1}^n d*\sum_{i=1}^n i^2 \varphi(i) =d=1ndi=1ni2φ(i)

整除分块然后用杜教筛求 f ( i ) = i 2 ∗ φ ( i ) f(i)=i^2*\varphi(i) f(i)=i2φ(i)的前缀和即可.( g ( i ) = i 2 ) g(i)=i^2) g(i)=i2)

#include
#include
#include
#define ll long long
using namespace std;
const int M=2222,N=M*M,mod=1000000007;

ll now,s[M];
bool vis[M];

int prime[N],tot;
ll phi[N];

ll upd(ll &x) {x+=x>>63&mod;}

void get(int x) {
	phi[1]=1;
	for(int i=2;i<=x;i++) {
		if(!phi[i]) phi[i]=i-1,prime[++tot]=i;
		for(int j=1;prime[j]*i<=x;j++) 
			if(i%prime[j]) phi[i*prime[j]]=phi[i]*(prime[j]-1);
			else {phi[i*prime[j]]=phi[i]*prime[j]; break;}
		upd(phi[i]+=phi[i-1]-mod);
	}
}


ll S(ll  n) {
	if(n<N) return phi[n];
	ll x=now/n,&ans=s[x];
	if(vis[x]) return ans;
	vis[x]=1; ans=n%mod; ans=ans*(ans+1)/2%mod;
	for(ll l=2,r;l<=n;l=r+1) {
		r=n/(n/l);
		upd(ans-=(r-l+1)*S(n/l)%mod);
	}
	return ans;
}

ll solve() {
	ll res=0;
	for(ll l=1,r,t;l<=now;l=r+1) {
		r=now/(t=now/l); t%=mod;
		upd(res+=(S(r)-S(l-1)+mod)*t%mod*t%mod-mod);
	}
	return res;
}

int main() {
	scanf("%lld",&now);
	get(N-1);
	printf("%lld\n",solve());return 0;
}

51nod 1239

51nod 1220

约数相关的函数定义 σ k \sigma^k σk表示约数的k次方和.特别地,k=0时表示约数个数.

一些性质:

  1. σ 0 ( i j ) = ∑ x ∣ i ∑ y ∣ j [ g c d ( x , y ) = 1 ] \sigma^0 (ij)=\sum_{x|i} \sum_{y|j}[gcd(x,y)=1] σ0(ij)=xiyj[gcd(x,y)=1].

    证明:设 i = ∏ p i a i , j = ∏ p i b i , g c d ( x , y ) = 1 i=\prod p_i^{a_i},j=\prod p_i^{b_i},gcd(x,y)=1 i=piai,j=pibi,gcd(x,y)=1说明对于任意质因数 p i , x , y p_i,x,y pi,x,y中至少有一个的指数为0,那么这样就一共有 a i + b i + 1 a_i+b_i+1 ai+bi+1种情况( x x x的指数为0时, y y y的指数有 b i + 1 b_i+1 bi+1种情况.反之亦然.去掉一个重复的 x , y x,y x,y指数都为0的情况即可),它与 i j ij ij p i p_i pi的合法指数数量一致.

  2. 通过上面的证明,我们不妨把 x x x的指数 p i p_i pi的非0指数 k k k映射为 k + b i k+b_i k+bi.

    这样我们得到一个新的式子:

    σ 1 ( i j ) = ∑ x ∣ i ∑ y ∣ j [ g c d ( x , y ) = 1 ] x ∗ j y \sigma^1 (ij)=\sum_{x|i} \sum_{y|j}[gcd(x,y)=1]x*\dfrac{j}{y} σ1(ij)=xiyj[gcd(x,y)=1]xyj.

    对每个质因子分开讨论,即可发现 j y \dfrac{j}{y} yj实际上就是在完成上面所述的映射.( y 的 指 数 非 0 , 那 么 实 际 对 应 的 指 数 在 [ 0 , b i ] 内 y的指数非0,那么实际对应的指数在[0,b_i]内 y0,[0,bi])

参考blog

这题的化式子特别恶心.前方高能

∑ i = 1 n ∑ j = 1 n ∑ x ∣ i ∑ y ∣ j [ g c d ( x , y ) = 1 ] x j y \sum_{i=1}^n \sum_{j=1}^n \sum_{x|i} \sum_{y|j} [gcd(x,y)=1]\dfrac{xj}{y} i=1nj=1nxiyj[gcd(x,y)=1]yxj

( 从 小 到 大 枚 举 变 量 ) ∑ d ∣ n μ ( d ) ∗ d ∗ ∑ x = 1 n / d ( ∑ x ∣ i 1 ) ∗ ∑ y = 1 n / d ( ⌊ n d y ⌋ + 1 ) ⌊ n d y ⌋ 2 (从小到大枚举变量)\sum_{d|n} \mu(d)*d* \sum_{x=1}^{n/d} (\sum_{x|i}1) *\sum_{y=1}^{n/d} \dfrac{(\lfloor\dfrac{n}{dy}\rfloor+1)\lfloor\dfrac{n}{dy}\rfloor}{2} ()dnμ(d)dx=1n/d(xi1)y=1n/d2(dyn+1)dyn

( 可 以 发 现 跟 x , y 有 关 的 式 子 都 可 转 化 为 约 数 个 数 和 ) ∑ d ∣ n μ ( d ) ∗ d ∗ ( ∑ x = 1 n / d σ 0 ( i ) ) 2 (可以发现跟x,y有关的式子都可转化为约数个数和)\sum_{d|n} \mu(d)*d* (\sum_{x=1}^{n/d} \sigma^0 (i))^2 (x,y)dnμ(d)d(x=1n/dσ0(i))2

#include
#include
#include
#define ll long long 
using namespace std;
const int M=1010,N=M*M,mod=1000000007,mod2=2*mod;

ll now,s[M];
bool vis[M];

int prime[N],tot,f[N];
ll h[N],mu[N];

void upd(ll &x) {x+=x>>63&mod;} 

void get(int x) {
	h[1]=mu[1]=1;
	for(int i=2;i<=x;i++) {
		if(!f[i]) f[i]=i,prime[++tot]=i,h[i]=1+i,mu[i]=-1;
		for(int j=1,k;(k=i*prime[j])<=x;j++) {
			if(i%prime[j]==0) {
				f[k]=f[i]*prime[j];
				if(f[k]==k) upd(h[k]=h[i]+k-mod);
				else h[k]=h[k/f[k]]*h[f[k]]%mod;
				break;
			}
			f[k]=prime[j];
			h[k]=h[i]*(prime[j]+1)%mod;
			mu[k]=-mu[i];
		}
	}
	for(int i=2;i<=x;i++)
		upd(h[i]+=h[i-1]-mod),mu[i]=(mu[i]*i+mu[i-1])%mod;
}

ll calc(ll x,ll y) {
	return (x+y)%mod2*((y-x+1)%mod2)/2%mod;
}

ll S(ll n) {
	if(n<N) return mu[n];
	ll x=now/n,&ans=s[x];
	if(vis[x]) return ans;
	vis[x]=1; ans=1;
	for(ll l=2,r;l<=n;l=r+1) {
		r=n/(n/l);
		(ans-=calc(l,r)*S(n/l)%mod) %= mod;
	}
	upd(ans);
	return ans;
}

ll g(ll n) {
	if(n<N) return h[n];
	ll ans=0;
	for(ll l=1,r;l<=n;l=++r) {
		r=n/(n/l);
		(ans+=calc(l,r)*(n/l)%mod) %= mod;
	}
	upd(ans);
	return ans;
}

ll solve() {
	ll ans=0,n=now;
	for(ll l=1,r;l<=n;l=++r) {
		r=(n/(n/l));
		ll t=g(n/l);
		t=t*t%mod;
		(ans+=(S(r)-S(l-1)+mod)*t%mod) %=mod;
	}
	return ans;
}

int main() {
	scanf("%lld",&now);
	get(min(now,(ll)N-1));
	printf("%lld\n",solve());
	return 0;
}

练习题表,以下对部分题目讲解

BZOJ #3512. DZY Loves Math IV

题意简洁,推导毒瘤:
给定 n , m , 求 ∑ i = 1 n ∑ j = 1 m φ ( i j ) , n ≤ 1 0 6 , m ≤ 1 0 9 n,m,求\sum_{i=1}^n \sum_{j=1}^m \varphi(ij),n\le 10^6,m\le 10^9 n,m,i=1nj=1mφ(ij),n106,m109.

与普通的反演不同的是,这题竟然是对每个 i i i,暴力求解.(其实也不是很暴力,就是和往常做法不同)

n ′ 表 示 n 的 质 因 数 的 乘 积 n'表示n的质因数的乘积 nn
{ s ( n , m ) = ∑ i = 1 m φ ( n i ) = n n ′ ∑ i = 1 m φ ( n ′ × i ) ( φ 性 质 ) = n n ′ ∑ i = 1 m φ ( n ′ gcd ⁡ ( n ′ , i ) × i × g c d ( n ′ , i ) ) = n n ′ ∑ i = 1 m φ ( n ′ gcd ⁡ ( n ′ , i ) ) φ ( i ) g c d ( n ′ , i ) ) ( n ′ 的 每 个 质 因 数 的 指 数 均 为 1 , 一 除 即 与 i gcd ⁡ 互 质 ) = n n ′ ∑ i = 1 m φ ( n ′ gcd ⁡ ( n ′ , i ) ) φ ( i ) ∑ d ∣ i , d ∣ n ′ φ ( d ) ( φ 反 演 ) = n n ′ ∑ i = 1 m φ ( i ) ∑ d ∣ i , d ∣ n ′ φ ( n ′ d gcd ⁡ ( n ′ , i ) ) ( d ⊥ gcd ⁡ ( n ′ , i ) = n n ′ ∑ i = 1 m φ ( i ) ∑ d ∣ i , d ∣ n ′ φ ( n ′ gcd ⁡ ( n ′ , i ) d ) = n n ′ ∑ i = 1 m φ ( i ) ∑ d ∣ i , d ∣ n ′ φ ( n ′ d ) = n n ′ ∑ d ∣ n ′ φ ( n ′ d ) ∑ i = 1 m d φ ( d i ) = n n ′ ∑ d ∣ n ′ φ ( n ′ d ) s ( d , m d ) . \begin{cases} s(n,m)&=\sum_{i=1}^m \varphi(ni) \\ &=\dfrac{n}{n'}\sum_{i=1}^m \varphi(n'\times i) (\varphi性质)\\ &=\dfrac{n}{n'}\sum_{i=1}^m \varphi(\dfrac{n'}{\gcd(n',i)}\times i\times gcd(n',i)) \\ &=\dfrac{n}{n'}\sum_{i=1}^m \varphi(\dfrac{n'}{\gcd(n',i)})\varphi(i)gcd(n',i))(n'的每个质因数的指数均为1,一除即与i\gcd互质) \\ &=\dfrac{n}{n'}\sum_{i=1}^m \varphi(\dfrac{n'}{\gcd(n',i)})\varphi(i)\sum_{d|i,d|n'}\varphi (d)(\varphi反演)\\ &=\dfrac{n}{n'}\sum_{i=1}^m\varphi(i)\sum_{d|i,d|n'} \varphi(\dfrac{n'd}{\gcd(n',i)})(d\bot\gcd(n',i) \\ &=\dfrac{n}{n'}\sum_{i=1}^m\varphi(i)\sum_{d|i,d|n'} \varphi(\dfrac{n'}{\frac{\gcd(n',i)}{d}})\\ &=\dfrac{n}{n'}\sum_{i=1}^m\varphi(i)\sum_{d|i,d|n'} \varphi(\dfrac{n'}{d})\\ &=\dfrac{n}{n'}\sum_{d|n'}\varphi(\dfrac{n'}{d})\sum_{i=1}^{\frac{m}{d}}\varphi(di)\\ &=\dfrac{n}{n'}\sum_{d|n'}\varphi(\dfrac{n'}{d})s(d,\dfrac{m}{d}). \end{cases} s(n,m)=i=1mφ(ni)=nni=1mφ(n×i)(φ)=nni=1mφ(gcd(n,i)n×i×gcd(n,i))=nni=1mφ(gcd(n,i)n)φ(i)gcd(n,i))(n1,igcd)=nni=1mφ(gcd(n,i)n)φ(i)di,dnφ(d)(φ)=nni=1mφ(i)di,dnφ(gcd(n,i)nd)(dgcd(n,i)=nni=1mφ(i)di,dnφ(dgcd(n,i)n)=nni=1mφ(i)di,dnφ(dn)=nndnφ(dn)i=1dmφ(di)=nndnφ(dn)s(d,dm).

一波操作猛如虎,智商顿增250

所以分治处理, m a p map map记忆化配合即可.
边界: n = 1 n=1 n=1,杜教筛求 φ \varphi φ前缀和即可.

复杂度不详,望读者给我点启发!!!

#include
#include
#include
#include
using namespace std;
typedef long long ll;
const int N=1.1e6+10,mod=1e9+7;

int n,m,prime[N/10],tot,low[N],f[N],phi[N];
void get() {
	low[1]=f[1]=phi[1]=1;
	for(int i=2;i<N;i++) {
		if(!low[i]) low[i]=i,f[i]=i-1,prime[++tot]=i;
		for(int j=1,k;(k=i*prime[j])<N;j++) 
			if(i%prime[j]) {
				low[k]=low[i]*prime[j];
				f[k]=f[i]*(prime[j]-1);
			}
			else {
				low[k]=low[i];
				f[k]=f[i]*prime[j];
				break;
			}
		phi[i]=f[i];
		f[i]=(f[i]+f[i-1])%mod;
	}
}

map<int,int>s;
ll S(ll n) {
	if(n<N) return f[n];
	if(s.count(n)) return s[n];
	ll ans=n*(n+1)/2%mod;
	for(ll l=2,r;l<=n;l=++r) {
		r=n/(n/l);
		ans -= (r-l+1) * S(n/l) % mod;
	}
	return s[n]=(ans%mod+mod)%mod;
}

map<int,int> ans[N];
ll C(int n,int m) {
	if(!m) return 0;
	if(ans[n].count(m)) return ans[n][m];
	if(n==1) return ans[n][m]=S(m);
	ll s=0;
	for(int i=1;i*i<=n;i++) 
		if(n%i==0) {
			s+=phi[n/i]*C(i,m/i)%mod;
			if(i*i!=n) s+=phi[i]*C(n/i,m/(n/i))%mod;
		}
	return ans[n][m]=s%mod;
}

int main() {
	scanf("%d%d",&n,&m); get();
	ll sum=0;
	for(int i=1;i<=n;i++) 
		sum+=i/low[i]*C(low[i],m)%mod;
	printf("%lld\n",sum%mod);return 0;
}

BZOJ #3930. [CQOI2015]选数

题意:在 选 n 个 [ l , r ] 中 的 数 ( 在 乎 顺 序 , 不 在 乎 相 同 ) , 求 gcd ⁡ = k 选n个[l,r]中的数(在乎顺序,不在乎相同),求\gcd=k n[l,r](,),gcd=k的方案数

方法1:直接套莫反公式,复杂度: O ( r 0.66 log ⁡ r + n log ⁡ m o d ) O(r^{0.66}\log r+\sqrt n\log mod) O(r0.66logr+n logmod)

#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define lc (x<<1)
#define rc (x<<1|1)
#define gc getchar()//(p1==p2&&(p2=(p1=buf)+fread(buf,1,size,stdin),p1==p2)?EOF:*p1++)
#define mk make_pair
#define pi pair
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
const int N=2e6+10,size=1<<20,mod=1000000007;

//char buf[size],*p1=buf,*p2=buf;
template<class o> void qr(o &x) {
	char c=gc; x=0; int f=1;
	while(!isdigit(c)){if(c=='-')f=-1; c=gc;}
	while(isdigit(c)) x=x*10+c-'0',c=gc;
	x*=f;
}
template<class o> void qw(o x) {
	if(x/10) qw(x/10);
	putchar(x%10+'0');
}
template<class o> void pr1(o x) {
	if(x<0)x=-x,putchar('-');
	qw(x); putchar(' ');
}
template<class o> void pr2(o x) {
	if(x<0)x=-x,putchar('-');
	qw(x); puts("");
}

int n,m,l,r,prime[N/10],tot,mu[N]; bool v[N];
void upd(int &x) {x+=x>>31&mod;}
void upd(ll &x) {x+=x>>63&mod;}

void get(int x) {
	mu[1]=1;
	for(int i=2;i<=x;i++) {
		if(!v[i]) prime[++tot]=i,mu[i]=-1;
		for(int j=1,k;(k=i*prime[j])<=x;j++) {
			v[k]=1;
			if(i%prime[j]) mu[k]=-mu[i];
			else break;
		}
		upd(mu[i]+=mu[i-1]);
	}
}

map<int,int>s;
ll S(int n) {
	if(n<N) return mu[n];
	if(s.count(n)) return s[n];
	ll ans=1;
	for(ll l=2,r;l<=n;l=++r) {
		r=n/(n/l);
		ans-=(r-l+1)*S(n/l);
	}
	upd(ans%=mod);
	return s[n]=ans;
}

ll power(ll a,ll b=n) {
	ll c=1;
	while(b) {
		if(b&1) c=c*a%mod;
		b /= 2; a=a*a%mod;
	}
	return c;
}

int main() {
	qr(n); qr(m); qr(l); qr(r); l=(l-1)/m; r /= m;
	get(min(r,N-1));ll ans=0;
	for(int i=1,j;i<=r;i=++j) {
		j=min(r/(r/i),l>=i?l/(l/i):(int)1e9);
		ans+=(S(j)-S(i-1)+mod)*power(r/i-l/i)%mod;
	}
	upd(ans%=mod);
	pr2(ans);
	return 0;
}


方法2:

首先, l = ⌊ l − 1 k ⌋ l=\lfloor\dfrac{l-1}{k}\rfloor l=kl1, r = ⌊ r k ⌋ r=\lfloor\dfrac{r}{k}\rfloor r=kr,之后题目转化为选择 ( l , r ] (l,r] (l,r]中的数使得 gcd ⁡ = 1 \gcd=1 gcd=1.

方法1没有利用到 l e n = r − l + 1 ≤ 1 0 5 + 1 len=r-l+1\le 10^5+1 len=rl+1105+1这一重要性质,其实直接容斥更快.

假如 n n n个数中有任意两个不同,由更相减损法得 gcd ⁡ ≤ l e n \gcd\le len gcdlen.
我们一开始计算的时候先忽略掉这个相等的影响.

我们容易求得 i ∣ gcd ⁡ i|\gcd igcd的总方案,然后把等于 gcd ⁡ = 2 i , 3 i , 4 i . . . . \gcd=2i,3i,4i.... gcd=2i,3i,4i....的减去即可.

#include
#define ll long long
using namespace std;
const int N=1e5+10,mod=1000000007;
int n,m,l,r,f[N],len;

ll power(ll a,ll b=n) {
	ll c=1;
	while(b) {
		if(b&1) c=c*a%mod;
		b /= 2; a=a*a%mod;
	}
	return c;
}

void upd(int &x) {x+=x>>31&mod; x-=(x>=mod?mod:0);}

int main() {
	scanf("%d %d %d %d",&n,&m,&l,&r); l=(l-1)/m; r /= m;
	for(int i=(len=r-l),x,y,last=-1,val; i;i--) {
		x=r/i; y=l/i;
		if(last!=x-y) last=x-y,val=power(last);//这一句的总共复杂度为sqrt(n)*log(n)
		upd(f[i]=val-last);//减去全部相等的方案.
		for(int j=i<<1;j<=len;j+=i) upd(f[i]-=f[j]);//O(len log(len))
	}
	printf("%d\n",(f[1]+(!l))%mod);return 0;//最后当然允许相等
}

你可能感兴趣的:(数论)