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).
原理:从大到小枚举质因数
参考博客
强烈建议学习之前做一些简单的莫比乌斯反演的题.(不用到杜教筛即可).
莫比乌斯反演题表
数论函数基础(前置知识):
复杂度: 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=1∑nf(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=1∑n(f∗g)(i)=i=1∑ng(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=1∑n(f∗g)(i)−i=2∑ng(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=1∑n(f∗g)(i)−i=2∑ng(i)∗S(⌊in⌋)
杜教筛的重点在于找到方便的 g g g,使得 g , f ∗ g g,f*g g,f∗g都是简单的积性函数.
在1s内可以跑过 n = 1 0 10 ∼ 11 n=10^{10\sim 11} n=1010∼11的数据.
复杂度证明:
有个性质:在求 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,nn,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=1ni+∑i=2nin)≈O(∑i=2nin)=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=2∑n1−cin)≈O(nc+∫0n1−cn/xdx)=O(nc+n1−2c).
此时用一下均值不等式: 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;
}
代码:
#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;
}
练习:
提示: g c d ( i , j ) = ∑ k ∣ i , k ∣ j φ ( k ) gcd(i,j)=\sum_{k|i,k|j}\varphi(k) gcd(i,j)=∑k∣i,k∣jφ(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∑nj=1∑nlcm(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=1∑nj=1∑ni∗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∑nd∗i=1∑n/dj=1∑n/di∗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∑nd∗i=1∑n/di∗j=1∑n/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=1∑nd∗[2(i=1∑n/di∗j=1∑ij∗[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∑nd∗[2(i=1∑n/di∗2i∗φ(i)+[i=1])−1](由更相减损法得j⊥i−>(i−j)⊥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=1∑nd∗i=1∑ni2φ(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;
}
约数相关的函数定义 σ k \sigma^k σk表示约数的k次方和.特别地,k=0时表示约数个数.
一些性质:
σ 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)=∑x∣i∑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 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的合法指数数量一致.
通过上面的证明,我们不妨把 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)=∑x∣i∑y∣j[gcd(x,y)=1]x∗yj.
对每个质因子分开讨论,即可发现 j y \dfrac{j}{y} yj实际上就是在完成上面所述的映射.( y 的 指 数 非 0 , 那 么 实 际 对 应 的 指 数 在 [ 0 , b i ] 内 y的指数非0,那么实际对应的指数在[0,b_i]内 y的指数非0,那么实际对应的指数在[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=1∑nj=1∑nx∣i∑y∣j∑[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} (从小到大枚举变量)d∣n∑μ(d)∗d∗x=1∑n/d(x∣i∑1)∗y=1∑n/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有关的式子都可转化为约数个数和)d∣n∑μ(d)∗d∗(x=1∑n/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;
}
练习题表,以下对部分题目讲解
题意简洁,推导毒瘤:
给定 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=1n∑j=1mφ(ij),n≤106,m≤109.
与普通的反演不同的是,这题竟然是对每个 i i i,暴力求解.(其实也不是很暴力,就是和往常做法不同)
设 n ′ 表 示 n 的 质 因 数 的 乘 积 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} ⎩⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎨⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎧s(n,m)=∑i=1mφ(ni)=n′n∑i=1mφ(n′×i)(φ性质)=n′n∑i=1mφ(gcd(n′,i)n′×i×gcd(n′,i))=n′n∑i=1mφ(gcd(n′,i)n′)φ(i)gcd(n′,i))(n′的每个质因数的指数均为1,一除即与igcd互质)=n′n∑i=1mφ(gcd(n′,i)n′)φ(i)∑d∣i,d∣n′φ(d)(φ反演)=n′n∑i=1mφ(i)∑d∣i,d∣n′φ(gcd(n′,i)n′d)(d⊥gcd(n′,i)=n′n∑i=1mφ(i)∑d∣i,d∣n′φ(dgcd(n′,i)n′)=n′n∑i=1mφ(i)∑d∣i,d∣n′φ(dn′)=n′n∑d∣n′φ(dn′)∑i=1dmφ(di)=n′n∑d∣n′φ(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;
}
题意:在 选 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+nlogmod)
#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=⌊kl−1⌋, 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=r−l+1≤105+1这一重要性质,其实直接容斥更快.
假如 n n n个数中有任意两个不同,由更相减损法得 gcd ≤ l e n \gcd\le len gcd≤len.
我们一开始计算的时候先忽略掉这个相等的影响.
我们容易求得 i ∣ gcd i|\gcd i∣gcd的总方案,然后把等于 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;//最后当然允许相等
}