将近一周的时间内,我专门学习了数论中有关积性函数求前缀和的一些方法,在被虐心的数论折磨得痛不欲生之后(然而我明明已经逃离了数学系为什么还要被数学虐啊摔!!),我终于基本掌握了这一方法,所以在这里记录一下基本的思想并放上一些题解,以便日后回顾。
积性函数是指这样一类数论函数,对于 ∀a,b∈N ∗ 且 gcd(a,b)=1 有 f(ab)=f(a)∗f(b) .常见的积性函数有:
若数论函数 f 对于 ∀a,b∈N ∗ 有 f(ab)=f(a)∗f(b) ,这样的 f 称为完全积性函数。常见的完全积性函数有:
Dirichlet 卷积的定义是,对于两个数论函数 f,g , f∗g(n)=∑ d|n f(d)g(nd ) , Dirichlet 卷积有如下的一些性质:卷积运算满足交换律、结合律、分配律,单位函数 ϵ 是 Dirichlet 卷积运算的单位元,两个积性函数的 Dirichlet 卷积仍然是积性函数。
∑ d|n φ(n)=n
∑ d|n μ(n)=[n=1] ([sth.]意思是括号中的条件成立取1,否则取0,以后还会经常碰到这个记号)
这两个公式在之后的推导中有极大的作用。
定理:若两个数论函数满足 f(n)=∑ d|n g(d) ,那么 g(n)=∑ d|n μ(d)f(nd ) 。在接下来的讨论中将会看到,这一定理对之后的许多讨论有重要的作用。
线性筛是指一种在线性时间复杂度内求出 n 以内所有正整数的最小质因子的算法。该算法描述如下:从2开始,若一个数没有被筛过,则它为质数,最小质因子为其本身,之后对所有的质数 j≤min[i] ,有 min[i∗j]=j 。这样,由于每个合数仅被其最小质因子筛去一次,所以算法的复杂度是线性的。
接下来,就是具体的求积性函数前缀和的算法了,但是我不打算把它详细的写出来,而是在题解中体现这样的思想。
首先,我们在线性筛的基础上改进一下就能在线性时间内求出积性函数的值。我们增加一个数组记录 n 的最小质因子的次数,在做线性筛的同时,利用积性函数的性质,就可以同时计算出它的值。设 f 是一个积性函数,首先根据定义计算 f(1) ,然后从2开始,先对 i 进行线性筛的操作,然后分两种情况:设 i 的最小质因子为 p ,次数为 k ,若 ip k >1 ,根据积性函数的性质有 f(i)=f(p k )∗f(ip k ) ,否则 i=p k ,这时需要根据 f 的定义来计算 f(i) (这种特殊情况下的计算一般能在 O(1) 内完成)。这样我们就在线性时间内完成了对一个积性函数值的计算。当然我们很容易同时计算其前缀和,这里无需多讲。
下面开始刷题时间~
有了前面线性筛的方法,我们很容易在 O(n) 的时间内完成这道题目,然而不幸的是,这道题的数据范围已经达到了 10 10 ,即使是线性的时间复杂度仍嫌太大。这样的话,就要用到下面介绍的神器——杜教筛了。
首先,我们来看一系列的公式推导。注意之前介绍的公式会经常出现哦:
记S(n)=∑ n i=1 μ(i)
∑ n i=1 ∑ d|i μ(d)
=∑ n i=1 [i=1]
=1
下面我们对开始的求和公式做一个小小的 trick ,我们来改变一下求和顺序,由于 i 能被 d 整除,我们考虑从1到 n 枚举 id :
=∑ n id =1 ∑ ⌊ndi ⌋ d=1 μ(d)
然后我们给变量换个名字:
=∑ n i=1 S(⌊ni ⌋)
=1
∴S(n)=1−∑ n i=2 S(⌊ni ⌋)
这时我们观察减号右边, i<n √ 时,不同的 S 只有 n √ 个, i≥n √ 时,由于 ni ≤n √ ,所以 ⌊ni ⌋ 也只有 n √ 个不同的值。这样我们就得到了一个关于 S 的递推式。事实上,我们还可以对前面一些项用线性筛来进行预处理。可以证明(懒得写了orz),预处理前 n 23 的前缀和能使复杂度最低,为 O(n 23 ) 。
#include
using namespace std;
const int N = 4641589;
map <long long,long long> m;
vector <int> prime;
int miu[N],sum[N],mini[N],tim[N];
long long power(long long num,long long index)
{
long long i,j,bi[60],ans = 1;
i = 0;
while (index)
{
bi[i++] = index%2;
index /= 2;
}
for (j=0;jif (bi[j])
ans *= num;
num *= num;
}
return ans;
}
long long mobius(long long x)
{
long long ans = 1,i,j = 0;
if (x < N)
return sum[x];
if (m.count(x))
return m[x];
for (i = 2;j < x;i = j + 1)
{
j = x / (x / i);
ans -= mobius(x / i) * (j - i + 1);
}
m[x]=ans;
return ans;
}
int main()
{
long long i,j,a,b;
scanf("%I64d%I64d",&a,&b);
miu[1] = 1;
sum[1] = 1;
memset(mini,0,sizeof(mini));
for (i = 2;i < N;i++)
{
if (!mini[i])
{
prime.push_back(i);
mini[i] = i;
}
for (j = 0;prime[j] <= mini[i] && prime[j] * i <= N && j < prime.size();j++)
mini[i*prime[j]]=prime[j];
if (mini[i] == i)
{
tim[i] = 1;
miu[i] = -1;
}
else if (mini[i] != mini[i / mini[i]])
{
tim[i] = 1;
miu[i] = miu[mini[i]] * miu[i / mini[i]];
}
else
{
tim[i] = tim[i / mini[i]] + 1;
if (i / power(mini[i],tim[i]) > 1)
miu[i] = miu[power(mini[i],tim[i])] * miu[i / power(mini[i],tim[i])];
else
miu[i] = 0;
}
sum[i] = sum[i-1] + miu[i];
}
printf("%I64d",mobius(b) - mobius(a-1));
return 0;
}
这道题和上一道题差不多,就只放公式和代码了。
记S(n)=∑ n i=1 φ(i)
∑ n i=1 ∑ d|i φ(d)
=∑ n i=1 i
=n(n+1)2
=∑ n i=1 S(⌊ni ⌋)
∴S(n)=n(n+1)2 −∑ n i=2 S(⌊ni ⌋)
#include
using namespace std;
const long long NN = 4641589;
const long long moder=1000000007;
map <long long,long long> m;
vector <long long> prime;
int phi[NN],mini[NN],tim[NN];
long long sum[NN];
long long power(long long num,long long index)
{
long long i,j,bi[60];
long long ans = 1;
i = 0;
while (index)
{
bi[i++] = index%2;
index /= 2;
}
for (j=0;jif (bi[j])
ans = num * ans % moder;
num = num * num % moder;
}
return ans;
}
long long euler(long long x)
{
long long ans = 0,i,j = 0;
if (x < NN)
return sum[x];
if (m.count(x))
return m[x];
for (i = 2;j < x;i = j + 1)
{
j = x / (x / i);
ans = (ans + euler(x / i) * ((j - i + 1) % moder)) % moder;
}
ans = (x % moder * ((x + 1) % moder) % moder * 500000004 % moder - ans + moder) % moder;
m[x] = ans;
return ans;
}
int main()
{
long long i,j,a;
scanf("%I64d",&a);
phi[1] = 1;
sum[1] = 1;
memset(mini,0,sizeof(mini));
for (i = 2;i < NN;i++)
{
if (!mini[i])
{
prime.push_back(i);
mini[i] = i;
}
for (j = 0;prime[j] <= mini[i] && prime[j] * i <= NN && j < prime.size();j++)
mini[i*prime[j]]=prime[j];
if (mini[i] == i)
{
tim[i] = 1;
phi[i] = i - 1;
}
else if (mini[i] != mini[i / mini[i]])
{
tim[i] = 1;
phi[i] = phi[mini[i]] * phi[i / mini[i]];
}
else
{
tim[i] = tim[i / mini[i]] + 1;
if (i / power(mini[i],tim[i]) > 1)
phi[i] = phi[power(mini[i],tim[i])] * phi[i / power(mini[i],tim[i])] % moder;
else
phi[i] = i / mini[i] * (mini[i] - 1);
}
sum[i] = (sum[i-1] + phi[i]) % moder;
}
printf("%I64d",euler(a));
return 0;
}
先证明一个引理: ∑ N i=1 ∑ N j=1 ij[gcd(i,j)=1]=∑ N i=1 i 2 φ(i)
∑ N i=1 ∑ N j=1 ij[gcd(i,j)=1]
=2∑ N i=1 ∑ i j=1 ij[gcd(i,j)=1]−∑ N i=1 i 2 [gcd(i,i)=1]
=2∑ N i=2 ∑ i−1 j=1 ij[gcd(i,j)=1]+∑ N i=2 ∑ i−1 j=1 i(i−j)[gcd(i,i−j)=1]+22 −1
=∑ N i=2 i 2 ∑ i−1 j=1 [gcd(i,j)=1]+1
=∑ N i=2 i 2 φ(i)+1
=∑ N i=1 i 2 φ(i)
和之前一样,我们来化简式子:
∑ N i=1 ∑ N j=1 lcm(i,j)
=∑ N i=1 ∑ N j=1 ijgcd(i,j)
设 i=i ′ r,j=j ′ r,gcd(i ′ ,j ′ )=1
=∑ N r=1 ∑ ⌊Nr ⌋ i=1 ∑ ⌊Nr ⌋ j=1 ijr 2 r [gcd(i,j)=1]
=∑ N r=1 r∑ ⌊Nr ⌋ i=1 ∑ ⌊Nr ⌋ j=1 ij[gcd(i,j)=1]
∑ N r=1 r∑ ⌊Nr ⌋ i=1 i 2 φ(i)
推到这里我们已经可以进行计算了,具体细节请看代码。
#include
using namespace std;
const long long NN = 4641589;
const long long moder=1000000007;
map <long long,long long> m;
vector <long long> prime;
long long phi[NN],mini[NN],tim[NN];
long long sum[NN];
long long sum1(long long x)
{
x = x % moder;
return x * (x + 1) % moder * 500000004 % moder;
}
long long sum2(long long x)
{
x = x % moder;
return x * (x + 1) % moder * (2 * x + 1) % moder * 166666668 % moder;
}
long long sum3(long long x)
{
long long ans = sum1(x);
return ans * ans % moder;
}
long long power(long long num,long long index)
{
long long i,j,bi[60];
long long ans = 1;
i = 0;
while (index)
{
bi[i++] = index % 2;
index /= 2;
}
for (j = 0;j < i;j++)
{
if (bi[j])
ans = num * ans % moder;
num = num * num % moder;
}
return ans;
}
long long euler(long long x)
{
long long ans = 0,i,j = 0;
if (x < NN)
return sum[x];
if (m.count(x))
return m[x];
for (i = 2;j < x;i = j + 1)
{
j = x / (x / i);
ans = (ans + euler(x / i) * (sum2(j) - sum2(i - 1) + moder)) % moder;
}
return m[x] = ans = (sum3(x) - ans + moder) % moder;
}
long long answer(long long x)
{
long long ans = 0,i,j = 0;
for (i = 1;j < x;i = j + 1)
{
j = x / (x / i);
ans = (ans + euler(x / i) * (sum1(j) - sum1(i - 1) + moder)) % moder;
}
return ans;
}
int main()
{
long long i,j,a;
scanf("%I64d",&a);
sum[0] = 0;
phi[1] = 1;
sum[1] = 1;
memset(mini,0,sizeof(mini));
for (i = 2;i < NN;i++)
{
if (!mini[i])
{
prime.push_back(i);
mini[i] = i;
}
for (j = 0;prime[j] <= mini[i] && prime[j] * i <= NN && j < prime.size();j++)
mini[i*prime[j]]=prime[j];
if (mini[i] == i)
{
tim[i] = 1;
phi[i] = i * i % moder * (i - 1) % moder;
}
else if (mini[i] != mini[i / mini[i]])
{
tim[i] = 1;
phi[i] = phi[mini[i]] * phi[i / mini[i]] % moder;
}
else
{
tim[i] = tim[i / mini[i]] + 1;
if (i / power(mini[i],tim[i]) > 1)
phi[i] = phi[power(mini[i],tim[i])] * phi[i / power(mini[i],tim[i])] % moder;
else
phi[i] = i / mini[i] * (mini[i] - 1) * i % moder * i % moder;
}
sum[i] = (sum[i-1] + phi[i]) % moder;
}
printf("%I64d",answer(a));
return 0;
}
这题和上一题类似,我也就不多说了。
∑ N i=1 ∑ N j=1 gcd(i,j)
设 i=i ′ r,j=j ′ r,gcd(i ′ ,j ′ )=1
=∑ N r=1 ∑ ⌊Nr ⌋ i=1 ∑ ⌊Nr ⌋ j=1 r[gcd(i,j)=1]
=∑ N r=1 r(2∑ ⌊Nr ⌋ i=1 ∑ i j=1 [gcd(i,j)=1]−1)
=∑ N r=1 r(2∑ ⌊Nr ⌋ i=1 φ(i)−1)
=2∑ N r=1 r∑ ⌊Nr ⌋ i=1 φ(i)−n(n+1)2
#include
using namespace std;
const long long NN = 4641589;
const long long moder=1000000007;
map <long long,long long> m;
vector <long long> prime;
long long phi[NN],mini[NN],tim[NN];
long long sum[NN];
long long sum1(long long x)
{
x = x % moder;
return x * (x + 1) % moder * 500000004 % moder;
}
long long sum2(long long x)
{
x = x % moder;
return x * (x + 1) % moder * (2 * x + 1) % moder * 166666668 % moder;
}
long long sum3(long long x)
{
long long ans = sum1(x);
return ans * ans % moder;
}
long long power(long long num,long long index)
{
long long i,j,bi[60];
long long ans = 1;
i = 0;
while (index)
{
bi[i++] = index % 2;
index /= 2;
}
for (j = 0;j < i;j++)
{
if (bi[j])
ans = num * ans % moder;
num = num * num % moder;
}
return ans;
}
long long euler(long long x)
{
long long ans = 0,i,j = 0;
if (x < NN)
return sum[x];
if (m.count(x))
return m[x];
for (i = 2;j < x;i = j + 1)
{
j = x / (x / i);
ans = (ans + euler(x / i) * ((j - i + 1) % moder)) % moder;
}
ans = (x % moder * ((x + 1) % moder) % moder * 500000004 % moder - ans + moder) % moder;
m[x] = ans;
return ans;
}
long long answer(long long x)
{
long long ans = 0,i,j = 0;
for (i = 1;j < x;i = j + 1)
{
j = x / (x / i);
ans = (ans + euler(x / i) * (sum1(j) - sum1(i - 1) + moder)) % moder;
}
return (2 * ans - sum1(x) + moder) % moder;
}
int main()
{
long long i,j,a;
scanf("%I64d",&a);
sum[0] = 0;
phi[1] = 1;
sum[1] = 1;
memset(mini,0,sizeof(mini));
for (i = 2;i < NN;i++)
{
if (!mini[i])
{
prime.push_back(i);
mini[i] = i;
}
for (j = 0;prime[j] <= mini[i] && prime[j] * i <= NN && j < prime.size();j++)
mini[i*prime[j]]=prime[j];
if (mini[i] == i)
{
tim[i] = 1;
phi[i] = i - 1;
}
else if (mini[i] != mini[i / mini[i]])
{
tim[i] = 1;
phi[i] = phi[mini[i]] * phi[i / mini[i]];
}
else
{
tim[i] = tim[i / mini[i]] + 1;
if (i / power(mini[i],tim[i]) > 1)
phi[i] = phi[power(mini[i],tim[i])] * phi[i / power(mini[i],tim[i])];
else
phi[i] = i / mini[i] * (mini[i] - 1);
}
sum[i] = (sum[i-1] + phi[i]) % moder;
}
printf("%I64d",answer(a));
return 0;
}
这道题。。怎么说呢。。还是跟前面两题没差啊。。所以只化简然后放代码:
∑ N i=1 ∑ i j=1 lcm(i,j)i
=∑ N i=1 ∑ i j=1 jgcd(i,j)
设 i=i ′ r,j=j ′ r,gcd(i ′ ,j ′ )=1
=∑ N r=1 ∑ ⌊Nr ⌋ i=1 ∑ i j=1 j[gcd(i,j)=1]
=∑ N r=1 ∑ ⌊Nr ⌋ i=1 ∑ i j=1 j[gcd(i,j)=1]
=N+∑ N r=1 ∑ ⌊Nr ⌋ i=1 iφ(i)2
=N+∑ N i=1 iφ(i)⌊Ni ⌋2
#include
using namespace std;
const long long NN = 4641589;
const long long moder=1000000007;
map <long long,long long> m;
vector <long long> prime;
long long mini[NN],tim[NN];
int sum[NN],phi[NN];
long long sum1(long long x)
{
x = x % moder;
return x * (x + 1) % moder * 500000004 % moder;
}
long long sum2(long long x)
{
x = x % moder;
return x * (x + 1) % moder * (2 * x + 1) % moder * 166666668 % moder;
}
long long sum3(long long x)
{
long long ans = sum1(x);
return ans * ans % moder;
}
long long power(long long num,long long index)
{
long long i,j,bi[60];
long long ans = 1;
i = 0;
while (index)
{
bi[i++] = index % 2;
index /= 2;
}
for (j = 0;j < i;j++)
{
if (bi[j])
ans = num * ans % moder;
num = num * num % moder;
}
return ans;
}
long long euler(long long x)
{
long long ans = 0,i,j = 0;
if (x < NN)
return sum[x];
if (m.count(x))
return m[x];
for (i = 2;j < x;i = j + 1)
{
j = x / (x / i);
ans = (ans + euler(x / i) * (sum1(j) - sum1(i-1))) % moder;
}
ans = (sum2(x) - ans + moder) % moder;
m[x] = ans;
return ans;
}
long long answer(long long x)
{
long long ans = 0,i,j = 0;
for (i = 1;j < x;i = j + 1)
{
j = x / (x / i);
ans = (ans + (x / i) * (euler(j) - euler(i - 1) + moder)) % moder;
}
return (ans + x) * 500000004 % moder;
}
int main()
{
long long i,j,a,b;
scanf("%I64d%I64d",&a,&b);
sum[0] = 0;
phi[1] = 1;
sum[1] = 1;
memset(mini,0,sizeof(mini));
for (i = 2;i < NN;i++)
{
if (!mini[i])
{
prime.push_back(i);
mini[i] = i;
}
for (j = 0;prime[j] <= mini[i] && prime[j] * i <= NN && j < prime.size();j++)
mini[i*prime[j]]=prime[j];
if (mini[i] == i)
{
tim[i] = 1;
phi[i] = (1LL) * (i - 1) * i % moder;
}
else if (mini[i] != mini[i / mini[i]])
{
tim[i] = 1;
phi[i] = (1LL) * phi[mini[i]] * phi[i / mini[i]] % moder;
}
else
{
tim[i] = tim[i / mini[i]] + 1;
if (i / power(mini[i],tim[i]) > 1)
phi[i] = (1LL) * phi[power(mini[i],tim[i])] * phi[i / power(mini[i],tim[i])] % moder;
else
phi[i] = (1LL) * i / mini[i] * (mini[i] - 1) * i % moder;
}
sum[i] = (1LL) * (sum[i-1] + phi[i]) % moder;
}
printf("%I64d",(answer(b) - answer(a - 1) + moder) % moder);
return 0;
}
这道题开始难度就比较大了,我们还是先来化简式子,首先我们不考虑x
\sum_{i=1}^{N}\sum_{j=1}^{i}\sum_{k=1}^{i}[lcm(j,k)=i] ∑ N i=1 ∑ i j=1 ∑ i k=1 [lcm(j,k)=i]
=\sum_{i=1}^{N}\sum_{j=1}^{i}\sum_{k=1}^{i}[\frac{jk}{gcd(j,k)}=i] =∑ N i=1 ∑ i j=1 ∑ i k=1 [jkgcd(j,k) =i]
=\sum_{i=1}^{N}\sum_{j=1}^{i}\sum_{k=1}^{i}[gcd(j,k)=\frac{jk}{i}] =∑ N i=1 ∑ i j=1 ∑ i k=1 [gcd(j,k)=jki ]
=\sum_{i=1}^{N}\sum_{j=1}^{i}\sum_{k=1}^{i}[gcd(\frac{i}{j},\frac{i}{k})=1] =∑ N i=1 ∑ i j=1 ∑ i k=1 [gcd(ij ,ik )=1]
=\sum_{i=1}^{N}\sum_{j=1}^{i}\sum_{k=1}^{i}[gcd(\frac{i}{j},\frac{i}{k})=1] =∑ N i=1 ∑ i j=1 ∑ i k=1 [gcd(ij ,ik )=1]
设 ij =j ′ ,ik =k ′ ,则 j ′ |i,k ′ |i∵gcd(j ′ ,k ′ )=1∴j ′ k ′ |i ,设 i=j ′ k ′ r
=∑ N j ′ =1 ∑ N k ′ =1 ∑ N r=1 [j ′ k ′ r≤N][gcd(j ′ ,k ′ )=1]
=∑ N i=1 ∑ N j=1 ∑ N k=1 [ijk≤N]∑ N d=1 μ(d)[d|gcd(j,k)]
=∑ N i=1 ∑ N j=1 ∑ N k=1 [ijk≤N]∑ N d=1 μ(d)[d|j][d|k]
设 j=j ′ r,k=k ′ r
=∑ N r=1 μ(r)∑ N i=1 ∑ N j ′ r=1 ∑ N k ′ r=1 [ij ′ k ′ r 2 ≤N]
=∑ N r=1 μ(r)∑ N i=1 ∑ ⌊Nr ⌋ j ′ =1 ∑ ⌊Nr ⌋ k ′ =1 [ij ′ k ′ ≤Nr 2 ]
事实上我们发现, r 最多取到 N − − √ ,而 i,j,k 各自的取值范围也不重要,只需要它们的积小于 Nr 2 :
=∑ N √ d=1 ∑ i ∑ j ∑ k [ijk≤⌊Nd 2 ⌋]
最后加上 i≤j 这一条件即可。
#include
using namespace std;
const int NN = 316228;
const int MM = 4642;
map <long long,long long> m;
vector <int> prime;
int miu[NN],sum[NN],mini[NN],tim[NN];
long long power(long long num,long long index)
{
long long i,j,bi[60],ans = 1;
i = 0;
while (index)
{
bi[i++] = index%2;
index /= 2;
}
for (j=0;jif (bi[j])
ans *= num;
num *= num;
}
return ans;
}
long long ans1(long long x)
{
long long i,ans = 0;
for (i = 1;i * i * i <= x;i++)
ans++;
return ans;
}
long long ans2(long long x)
{
long long i,ans = 0;
for (i = 1;i * i <= x;i++)
ans += x / i /i;
return ans * 3;
}
long long ans3(long long x)
{
long long i,j,x2,ans = 0,ans1;
for (i = 1;power(i,3) <= x;i++)
{
ans1 = 0;
x2 = x / i;
for (j = i + 1;j * j <= x2;j++)
ans1 += x2 / j - j;
ans += ans1;
}
return ans * 6;
}
long long answer(long long x)
{
long long i,j = 0,ans = 0;
for (i = 1;j * j < x && i * i <= x;i = j + 1)
{
j = sqrt(x / (x / (i * i)));
ans += (sum[j] - sum[i - 1]) * (-2 * ans1(x / i / i) + ans2(x / i / i) + ans3(x / i / i));
}
return (ans + x) / 2;
}
int main()
{
long long i,j,a,b;
scanf("%I64d%I64d",&a,&b);
sum[0] = 0;
miu[1] = 1;
sum[1] = 1;
memset(mini,0,sizeof(mini));
for (i = 2;i < NN;i++)
{
if (!mini[i])
{
prime.push_back(i);
mini[i] = i;
}
for (j = 0;prime[j] <= mini[i] && prime[j] * i <= NN && j < prime.size();j++)
mini[i*prime[j]]=prime[j];
if (mini[i] == i)
{
tim[i] = 1;
miu[i] = -1;
}
else if (mini[i] != mini[i / mini[i]])
{
tim[i] = 1;
miu[i] = miu[mini[i]] * miu[i / mini[i]];
}
else
{
tim[i] = tim[i / mini[i]] + 1;
if (i / power(mini[i],tim[i]) > 1)
miu[i] = miu[power(mini[i],tim[i])] * miu[i / power(mini[i],tim[i])];
else
miu[i] = 0;
}
sum[i] = sum[i-1] + miu[i];
}
printf("%I64d",answer(b) - answer(a-1));
return 0;
}