筛法学习笔记

埃式筛法

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) .

线性筛

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) .
原理:从大到小枚举质因数

杜教筛

参考博客

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

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

  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次方和)
  2. 积性函数: n m n\bot m ,则有 f ( n m ) = f ( n ) × f ( m ) f(nm)=f(n)\times f(m) ,则为积性函数. 以上函数皆为积性函数.
  3. 一些性质. f , g f,g为积性函数 , h ( n ) = f ( n ) × g ( n ) h(n)=f(n)\times g(n) 显然也是积性函数.
  4. 积性函数的运算: + , , , +,-,*, 求逆.
  5. 整除分块

复杂度: O ( n 2 3 O(n^{\frac{2}{3}} ).
用途:求积性函数前缀和:
已知积性函数 f f ,求 S ( n ) = i = 1 n f ( i ) S(n)=\sum\limits_{i=1}^n f(i) .
处理技巧:
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)
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 \because g是积性函数
g ( 1 ) = 1 \therefore 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)

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

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

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

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}

递归到 x x 的时候,前面的前缀和一定被计算过了.
可以发现,直接用 x \sqrt 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 ( 1 ) O(1) ,可以不用根号复杂度,
所以我们设对前 n c ( c ( 0 , 1 ) ) n^c(c\in(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}}) .

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

Tips:答案记忆化才能保证复杂度.下面提供一种不用 m a p 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 .
  2. f = μ , g = i d , f g = φ f=\mu,g=id,f*g=\varphi

代码:

#include<cstdio>
#include<cstring>
#include<algorithm>
#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)
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 = 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]
= 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 = 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 = 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 = 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 = 1 n d i = 1 n i 2 φ ( i ) =\sum_{d=1}^n d*\sum_{i=1}^n i^2 \varphi(i)

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

#include<cstdio>
#include<cstring>
#include<algorithm>
#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=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] .

    证明:设 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 说明对于任意质因数 p i , x , y p_i,x,y 中至少有一个的指数为0,那么这样就一共有 a i + b i + 1 a_i+b_i+1 种情况( x x 的指数为0时, y y 的指数有 b i + 1 b_i+1 种情况.反之亦然.去掉一个重复的 x , y x,y 指数都为0的情况即可),它与 i j ij p i p_i 的合法指数数量一致.

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

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

    σ 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} .

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

参考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}

( ) 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}

( 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

#include<cstdio>
#include<cstring>
#include<algorithm>
#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 .

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

n n n'表示n的质因数的乘积
{ 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}

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

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

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

#include<map>
#include<cstdio>
#include<cstring>
#include<algorithm>
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 的方案数

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

#include<map>
#include<set>
#include<queue>
#include<cmath>
#include<cstdio>
#include<vector>
#include<string>
#include<cstring>
#include<iostream>
#include<algorithm>
#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<int,int>
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 , r = r k r=\lfloor\dfrac{r}{k}\rfloor ,之后题目转化为选择 ( l , r ] (l,r] 中的数使得 gcd = 1 \gcd=1 .

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

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

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

#include<cstdio>
#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;//最后当然允许相等
}

猜你喜欢

转载自blog.csdn.net/qq_42886072/article/details/105394812
今日推荐