说一下积性函数咯.
积性函数(Multiplicative function)就是满足 f(1)=1 且 f(ab)=f(a)∗f(b) 的函数,而且它在数论中也起到比较大的作用.. 常见的有欧拉函数 ϕ 、默比乌斯函数 μ 、约数个数 σ 、只有1才等于1的函数 ϵ ..
欧拉函数是积性函数中算是比较多用的函数了, ϕ(n) 表示与 n 互质且比 n 小的数的个数,例如: ϕ(8)=4 、 ϕ(64)=16 、 ϕ(100)=40 ..
欧拉函数有以下几个性质:
对于正整数 a 和 n ,若 (a,n)=1 ,那么 aϕ(n)≡1(modn)
下面有一个简单的推论:
在 (a,n)=1 的情况下,如果有 ax≡ay(modn) ,那么定有 x≡y(modϕ(n))
有一些公式是比较常用的,下面给出:
虽然这些公式都非常简单,但是能真正在做题的时候用到并不是一件容易的事,希望所有来看这篇博文的人都注意一下
题意:求 ∑1≤i≤nk mod i (n≤109)
∑1≤i≤nk mod i
=∑1≤i≤n⌊ki⌋i
=nk−∑1≤i≤n⌊ki⌋i
由于 ⌊ki⌋ 对于某些不同的 i 是会相同的,而且对于从 1 到 n 是一个非递增的序列,所以我们可以用分块来解决
如果 i 是当前这一块最靠左的端点,那么 j=k⌊ki⌋ 便是这一块最靠右的端点
这样的分块计算答案的时间复杂度为 O(n−−√)
code
/************************************************************** Problem: 1257 Language: C++ Result: Accepted Time:8 ms Memory:804 kb ****************************************************************/
#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <algorithm>
#define LL long long
using namespace std;
LL n, K;
int main (){
LL i, j, k;
scanf ( "%lld%lld", &n, &K );
LL sum = 0;
i = 1;
while ( i <= n ){
k = K/i;
if ( k == 0 ) j = n;
else j = K/k;
if ( j > n ) j = n;
sum += (i+j)*(j-i+1)/2*k;
i = j+1;
}
printf ( "%lld\n", K*n-sum );
return 0;
}
题意:求 ∑1≤i≤n(i,n) (n≤109)
枚举 d=(i,n) ,设 i=i′d
∑1≤i≤n(i,n)
=∑d|n∑i′d≤n,(i′d,n)=dd
=∑d|nd∑i′≤nd,(i′,nd)=11
由于 ∑i′≤nd,(i′,nd)=11 便是求与 nd 互质且比它小的数的个数,所以
=∑d|ndϕ(nd)
那么我们只需要枚举d即可,时间复杂度 O(n−−√)
code
/************************************************************** Problem: 2705 Language: C++ Result: Accepted Time:16 ms Memory:1300 kb ****************************************************************/
#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <algorithm>
#include <cmath>
#include <vector>
#define LL long long
using namespace std;
LL n;
LL nu[31000], sum[31000], tot, ans;
void dfs ( LL x, LL phi, LL num ){
if ( x == tot+1 ) ans += n/num*phi;
else {
LL pf = 1;
dfs ( x+1, phi, num );
for ( LL i = 1; i <= sum[x]; i ++ ){
dfs ( x+1, phi*pf*(nu[x]-1), num*pf*nu[x] );
pf *= nu[x];
}
}
}
int main (){
LL i, j, k;
scanf ( "%lld", &n );
tot = 0;
LL x = n;
for ( i = 2; i <= sqrt(n); i ++ ){
if ( x % i == 0 ){
tot ++;
nu[tot] = i;
while ( x % i == 0 ){ sum[tot] ++; x /= i; }
}
}
tot ++;
nu[tot] = x; sum[tot] = 1;
ans = 0;
dfs ( 1, 1, 1 );
printf ( "%lld\n", ans );
return 0;
}
题意:求 ∑1≤i≤n[i,n] (n≤107)
枚举 d 令 (i,n)=d ,设 i=i′d
∑1≤i≤n[i,n]
=∑d|n∑i′≤nd,(i′d,n)=di′dnd
=∑d|n∑i′≤nd,(i′,nd)=1i′n
=n∑d|n∑i′≤nd,(i′,nd)=1i′
这里要用到这个公式:
∑d≤n,(d,n)=1d=ϕ(d)d2
但是当 n=1 的时候并不满足上述公式,少了 12 ,所以我们可以把它加上(反正其他的 n 加上 12 也不会有什么变化)
原式 =n(12+∑d|nϕ(nd)nd2)
=n(1+∑d|nϕ(nd)nd)2
我们可以在线性时间内求出 ϕ(nd) 的值,同时我们也可以在 O(nlogn) 的时间内求出 ∑d|nϕ(nd)nd 的值(有 O(n) 的方法,但是我好像不会0.0)
所以这道题就变成了一个 O(nlogn) 预处理、 O(1) 询问的问题
code
/************************************************************** Problem: 2226 Language: C++ Result: Accepted Time:7308 ms Memory:27660 kb ****************************************************************/
#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <algorithm>
#include <cmath>
#define LL long long
using namespace std;
const int Maxn = 1100000;
LL n;
LL phi[Maxn], prime[Maxn], pr;
LL ans[Maxn];
bool v[Maxn];
void ss (){
memset ( v, true, sizeof (v) );
int i, j;
phi[1] = 1;
for ( i = 2; i <= 1000000; i ++ ){
if ( v[i] == true ){
prime[++pr] = i;
phi[i] = i-1;
}
for ( j = 1; j <= pr && i*prime[j] <= 1000000; j ++ ){
v[i*prime[j]] = false;
if ( i % prime[j] == 0 ){
phi[i*prime[j]] = phi[i] * prime[j];
break;
}
else phi[i*prime[j]] = phi[i] * phi[prime[j]];
}
}
for ( i = 1; i <= 1000000; i ++ ){
for ( j = i; j <= 1000000; j += i ){
ans[j] += i*phi[i];
}
}
}
int main (){
LL i, j, k;
LL T;
scanf ( "%lld", &T );
ss ();
while ( T -- ){
scanf ( "%lld", &n );
printf ( "%lld\n", (ans[n]+1)*n/2 );
}
return 0;
}
题意:求 ∑1≤i≤n∑1≤j≤m2((i,j)−1)+1 (1≤n,m≤105)
我们可以先简化公式
∑1≤i≤n∑1≤j≤m2((i,j)−1)+1
=2∑1≤i≤n∑1≤j≤m(i,j)−nm
那么问题就简化为求 ∑1≤i≤n∑1≤j≤m(i,j) 了
∑1≤i≤n∑1≤j≤m(i,j)
=∑1≤i≤n∑1≤j≤m∑d|(i,j)ϕ(d)
=∑dϕ(d)∑i′≤nd∑j′≤md1
=∑dϕ(d)⌊nd⌋⌊md⌋
由于 ⌊nd⌋⌊md⌋ 在某一段的 d 会保持不变,所以我们可以用分块来解决,而 ϕ(d) 我们可以在线性时间内筛出预处理即可
code
/************************************************************** Problem: 2005 Language: C++ Result: Accepted Time:80 ms Memory:2632 kb ****************************************************************/
#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <algorithm>
#include <cmath>
#define LL long long
using namespace std;
const LL Maxn = 110000;
LL n, m;
LL phi[Maxn], prime[Maxn], pr;
bool v[Maxn];
LL _min ( LL x, LL y ){ return x < y ? x : y; }
void ss (){
phi[1] = 1;
LL i, j;
for ( i = 2; i <= 100000; i ++ ){
if ( v[i] == false ){
prime[++pr] = i;
phi[i] = i-1;
}
for ( j = 1; j <= pr && i*prime[j] <= 100000; j ++ ){
v[i*prime[j]] = true;
if ( i % prime[j] == 0 ){
phi[i*prime[j]] = phi[i]*prime[j];
break;
}
else phi[i*prime[j]] = phi[i]*phi[prime[j]];
}
}
for ( i = 2; i <= 100000; i ++ ) phi[i] += phi[i-1];
}
int main (){
LL d, i, j, k;
scanf ( "%lld%lld", &n, &m );
if ( n < m ) swap ( n, m );
ss ();
d = 1;
LL ans = 0;
while ( d <= m ){
LL kn = n/d, km = m/d;
LL jn, jm;
if ( kn == 0 ) jn = n;
else jn = n/kn;
if ( jn > n ) jn = n;
if ( km == 0 ) jm = m;
else jm = m/km;
if ( jm > m ) jm = m;
j = _min ( jn, jm );
ans += (phi[j]-phi[d-1])*kn*km;
d = j+1;
}
printf ( "%lld\n", ans*2-n*m );
return 0;
}
题意:求 ∑1≤i≤n∑1≤j≤m[(i,j)=d](1≤d≤n,m≤5×104)
设 i=i′d 、 j=j′d
∑1≤i≤n∑1≤j≤m[(i,j)=d]
=∑i′≤nd∑j′≤md[(i′,j′)=1]
=∑i′≤nd∑j′≤mdϵ(i′,j′)
来一波莫比乌斯反演:
ϵ(n)=∑d|nμ(d)
所以
=∑i′≤nd∑j′≤md∑k|(i′,j′)μ(k)
继续设 i′=i′′k 、 j′=j′′k
那么我们又可以继续推到
=∑kμ(k)∑i′′≤ndk∑j′′≤mdk1
这又回到了一个比较经典的问题:
=∑kμ(k)⌊ndk⌋⌊mdk⌋
我们可以看出 μ(k) 可以在线性时间内筛出,然后剩下的可以用分块解决0.0
code
/************************************************************** Problem: 1101 Language: C++ Result: Accepted Time:11132 ms Memory:1652 kb ****************************************************************/
#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <algorithm>
#define LL long long
using namespace std;
const LL Maxn = 51000;
LL n, m;
LL mu[Maxn], prime[Maxn], pr;
bool v[Maxn];
LL _min ( LL x, LL y ){ return x < y ? x : y; }
void get_mu (){
LL i, j, k;
pr = 0;
mu[1] = 1;
memset ( v, false, sizeof (v) );
for ( i = 2; i <= 50000; i ++ ){
if ( v[i] == false ){
prime[++pr] = i;
mu[i] = -1;
}
for ( j = 1; j <= pr && i*prime[j] <= 50000; j ++ ){
v[i*prime[j]] = true;
if ( i % prime[j] == 0 ){
mu[i*prime[j]] = 0;
}
else mu[i*prime[j]] = -mu[i];
}
}
for ( i = 2; i <= 50000; i ++ ) mu[i] += mu[i-1];
}
int main (){
LL i, j, k, T, d, ans;
scanf ( "%lld", &T );
get_mu ();
while ( T -- ){
scanf ( "%lld%lld%lld", &n, &m, &d );
if ( n < m ) swap ( n, m );
n /= d; m /= d;
i = 1; ans = 0;
while ( i <= m ){
LL nk = n/(n/i), mk = m/(m/i);
j = _min ( nk, mk );
ans += (n/i)*(m/i)*(mu[j]-mu[i-1]);
i = j+1;
}
printf ( "%lld\n", ans );
}
return 0;
}
题意:求 ∑1≤i≤n∑1≤j≤m[i,j] (n≤107)
∑1≤i≤n∑1≤j≤m[i,j]
=∑1≤i≤n∑1≤j≤mij(i,j)
枚举 (i,j)=d ,设 i=i′d 、 j=j′d
=∑d∑i′≤nd∑j′≤mdi′dj′dd[(i′,j′)=1]
=∑dd∑i′≤nd∑j′≤mdi′j′×ϵ(i′,j′)
再来一波莫比乌斯反演
=∑dd∑i′≤nd∑j′≤mdi′j′∑k|(i′,j′)μ(k)
那么我们再枚举 k ,设 i′′=i′k 、 j′′=j′k
=∑dd∑kμ(k)∑i′′≤ndk∑j′′≤mdki′′kj′′k
=∑dd∑kμ(k)k2∑i′′≤ndk∑j′′≤mdki′′j′′
=14∑dd∑kμ(k)k2(1+⌊ndk⌋)⌊ndk⌋(1+⌊mdk⌋)⌊mdk⌋
那么上式既枚举了 d ,也枚举了 k ,而且 d 和 k 没有必然的联系,这样的式子是不能在线性时间内实现的
所以我们设 D=dk ,通过枚举 D 来解决我们的问题
=14∑D∑d|Ddμ(Dd)(Dd)2(1+⌊nD⌋)⌊nD⌋(1+⌊mD⌋)⌊mD⌋
进一步化简该式子
=14∑D(1+⌊nD⌋)⌊nD⌋(1+⌊mD⌋)⌊mD⌋∑d|Dμ(Dd)D2d
这个式子的后半段是可以在线性时间筛出(其实我不太懂,是翁神教我的),然后前半段又可以通过分块来解决,所以该做法的时间复杂度为 O(n)−O(n−−√) 的
先写这么多吧,更难的以后再补了