莫比乌斯与积性函数

莫比乌斯与积性函数

之前做过不少的数论题,关于莫比乌斯与积性函数的数论题挺多的。。。特地过来总结一下。。当作自己的一个回顾了-_-

先安利一下神犇tls的博客和神犇PoPoQQQ的pdf !
膜拜tls…
跪popoqqq…
还有IOI金牌神犇任之州的集训队论文,都是好文啊!

需要先知道线性筛这个东西。。
orz…
线性筛的思想是每个合数都只会被它最小的质因数筛去,通过线性筛,我们可以O(n)得到1到n内一些数论函数的值,比如说欧拉函数、莫比乌斯函数、因子个数等等。。。

本文的除法均为整除,除非特别指出。
[expresion]为bool表达式,值为0或1,当且仅当expresion为真时为1,否则为0。
(i,j)表示gcd(i,j)。

莫比乌斯反演

莫比乌斯函数

首先定义莫比乌斯函数

u(i)=1,(1)k,0,if n = 1if n=p1p2...pk 

根据上面的定义,n大于1时且n是平方因子数时莫比乌斯函数值为0,否则从n的唯一分解定理中根据素数的个数取奇偶即可。

莫比乌斯函数的性质:

1) d|nu(d)=1,0,if n = 1
有了上述这个式子,我们就可以直接简单地以O(nlogn)筛出1到n内所有数的莫比乌斯函数值了~
至此我们已经有两种办法求1到n内所有数的欧拉函数值了。

//O(n)
bool vis[N];
int primes[N], miu[N];
int init(int n) {
    int tot = 0;
    miu[1] = 1;
    for (int i = 2; i <= n; i++) {
        if (!vis[i]) {
            primes[tot++] = i;
            miu[i] = -1;
        }
        for (int j = 0; j < tot; j++) {
            int k = i * primes[j];
            if (k > n)break;
            vis[k] = true;
            if (i % primes[j])  miu[k] = -miu[i];
            else break;
        }
    }
}
//O(nlogn)
void init(int n) {
    miu[1] = 1;
    int t = n >> 1;
    for (int i = 1; i <= t; i++) if (miu[i]) {
        for (int j = i << 1; j <= n; j += i) miu[j] -= miu[i];
    }
}

2) d|nu(d)d=φ(n)n

莫比乌斯函数除了可以用在莫比乌斯反演中之外,还可以用来进行容斥。
举一个常见的例子,求取1到n这个区间内有多少个数与x互质,一般的做法是直接进行容斥,但是我们可以发现容斥的系数刚好是莫比乌斯函数,即ans = d|xu(d)nd ,其实这两者从本质考虑是完全等价的。

莫比乌斯反演

莫比乌斯反演是一个这样的式子:
定义 F(n)=d|nf(n) ,那么可以得到 f(n)=d|nu(nd)F(d)
莫比乌斯反演还有一种更常见的形式: F(n)=n|df(d) , 那么有 f(n)=n|du(dn)F(d)
一般应用的都是上述的第二种形式,证明可以通过归纳得出,也可以直接通过式子变换得出,还可以由狄利克雷卷积证明。
f(n)=d|nu(nd)F(d)=d|nu(nd)x|df(x)=d|nf(d)x|ndu(x)=f(n)
上述的证明中给出了一种常见的和式变换技巧:交换求和顺序。通过交换求和顺序,我们往往可以将某些和式化简或者更容易求出该和式,后面公式的化简将多次用到。

莫比乌斯反演还有一个推广式,如下:
莫比乌斯与积性函数_第1张图片

积性函数

积性函数定义

f(x) 为一个数论函数,如果对于任意正整数a、b满足 (a,b)=1 ,有 f(ab)=f(a)f(b) 的话,我们称 f(n) 为积性函数;如果对于任意正整数啊a、b有 f(ab)=f(a)f(b) 的话,我们称 f(n) 为完全积性函数。
常见的积性函数有:
因子个数函数 d(n) ,因子和函数 σ(n) ,二元函数 gcd(a,b) ,欧拉函数 φ(n) ,莫比乌斯函数 u(n)
完全积性函数有:
元函数 e(n)=[n==1] ,恒等函数 I(n)=1 ,单位函数 id(n)=n

积性函数应用

我们来看看积性函数的应用:
如果 f(x) 为积性函数且 n=ti=1peii ,那么可以得到 f(n)=ti=1f(peii) ,如果f(x)为完全积性函数,那么我们还可以进一步得到 f(n)=ti=1f(pi)ei
举个简单的例子:因子个数 d(n)=ti=1d(peii)=ti=1(ei+1) ,因子和函数 σ(n)=ti=1σ(peii)=ti=1piei+11pi1
积性函数还可以用来进行线性筛从而得到很多数论函数的值~
比如下面的欧拉函数:
可以知道当 i%p==0 时, φ(ip)=φ(i)p ,而当 i%p!=0 时,有

φ(ip)=φ(i)φ(p)=φ(i)(p1)

const int N = 1e6 + 5;
bool vis[N];
int phi[N], p[N], cnt = 0;
void seive() {
    cnt = 0;
    phi[1] = 1;
    for (int i = 2; i < N; i++) {
        if (!vis[i]) p[cnt++] = i, phi[i] = i - 1;
        for (int j = 0; j < cnt; j++) {
            int s = i * p[j];
            if (s > N) break;
            vis[s] = 1;
            if (i % p[j] == 0) {
                phi[s] = p[j] * phi[i];
                break;
            }
            phi[s] = (p[j] - 1) * phi[i];
        }
    }
}

积性函数前缀和

积性函数的和也是积性函数,积性函数前缀和是一个常见的问题,常常需要低于线性时间内解决。

狄利克雷卷积

狄利克雷卷积是解决积性函数前缀和问题的一个重要工具。

定义

对两个算术函数f, g,定义其Dirichlet卷积为新函数f * g,满足 (fg)(n)=d|nf(d)g(nd)
狄利克雷卷积满足以下定律:
莫比乌斯与积性函数_第2张图片
若f,g均为积性函数,则f*g也是积性函数。
我们可以O(nlogn)预处理两个函数的Dirichlet卷积。

LL f[N], g[N], h[N];
void calc(int n) {
    for (int i = 1; i * i <= n; i++) {
        h[i * i] += f[i] * g[i];
        for (int j = i + 1; i * j <= n; j++) h[i * j] += f[i] * g[j] + f[j] * g[i];
    }
}
举例

很多数论函数都可以用狄利克雷卷积来表示:
①约数个数函数 d(n)=(II)(n) ;
②约数和函数 d(n)=(Iid)(n) ;
d|nu(d)=[n==1] ,得到 Iu=e
d|nφ(d)=n ,得到 Iφ=id

现在我们可以通过狄利克雷卷积来证明莫比乌斯反演了:
如果 F(n)=d|nf(n) ,那么F = I*f,那么可以知道F*u = I*f*u=f*(I*u)=f*e=f,所以 f(n)=d|nu(nd)F(d)

应用

设f(n)为一个数论函数,需要计算 S(n)=ni=1f(i) ,通常n>=1e9。
要解决上述问题,我们可以构造一个 S(n) 关于 S(ni) 的递推式。
找到一个合适的数论函数g(n),

i=1nd|if(d)g(id)=i=1ng(i)j=1nif(j)=i=1ng(i)S(ni)

这样,可以知道 g(1)S(n)=ni=1(fg)(i)ni=2g(i)S(ni)
构造的卷积函数必须容易求得前缀和,这样我们可以记忆化地计算S(n),因为 ni 是分段的,可以计算出复杂度为 O(n34) ,只要我们预处理S函数前 23 的值,复杂度就可以达到 O(n23)
上述的办法就是传说中的杜教筛了~
如果找不到适合的函数g(n),那么可以采用一种复杂度为 O(n34logn) 的办法来实现求和,详见任之州的论文。

下面给出求积性函数前缀和时常用到的一些公式和结论:
d|nu(d)=[n==1]
d|nφ(d)=n
ni=1i[(i,n)==1]=nφ(n)+[n==1]2
d(n2)=d|n2w(d)=d|ni|du2(i) ,w(d)表示d的不同质因子的个数。
d(nm)=i|nj|m[(i,j)==1]

i=1n[(i,n)==1](i1,n)=d|ni=1n[(i,n)==1][d|(i1)]=d|nφ(d)φ(n)φ(d)=d(n)φ(n)

d|ijd(i,d)|j
上面的几个东西有些是很显然的,但是还是写出来放在一起好了,至于第六个本人能力过弱不知如何证明。。。

习题

bzoj 2420 完全平方数
传送门
题意:求第n个无平方因子数。
分析:问题等价于求最小的x使得1到x内有n个无平方因子数,我们考虑容斥,总数减掉2^2的倍数个数,3^2的倍数个数,5^2的倍数个数….加上(2*3)^2的倍数个数,加上(3*5)^2的倍数个数,加上(3*5)^2的倍数个数…..可以发现容斥的系数就是莫比乌斯函数。这样我们二分x,然后 x 求个数即可。
不过注意到m/(i*i)的值是分块的,所以我们可以分块求,所以复杂度下界大概是 x

#include 
using namespace std;

typedef long long LL;
const int N = 41005;
bool vis[N];
int primes[N], miu[N];

int init(int limit) {
    int tot = 0;
    miu[1] = 0;
    for (int i = 2; i <= limit; i++) {
        if (!vis[i]) {
            primes[tot++] = i;
            miu[i] = 1;
        }
        for (int j = 0; j < tot; j++) {
            int k = i * primes[j];
            if (k > limit) break;
            vis[k] = true;
            if (i % primes[j]) miu[k] = -miu[i];
            else break;
        }
        miu[i] += miu[i-1];
    }
}
LL cal(LL m) {
    LL s = 0;
    for (LL i = 1, last; i * i <= m; i = last + 1) {
        LL x = m / (i * i); last= sqrt(m / x);
        s += (miu[last] - miu[i-1]) * x;
    }
    return m - s;
}
int main() {
    init(41000);
    int t;
    scanf("%d", &t);
    while (t--) {
        LL k;
        scanf("%lld", &k);
        LL l = 0, r = 1644934090LL;
        while (r - l > 1) {
            LL m = (r + l) >> 1;
            cal(m) >= k ? r = m : l = m;
        }
        printf("%lld\n", r);
    }
    return 0;
}

bzoj 2301 Problem b
传送门
题意:对于给出的 n 个询问,每次求有多少个数对 (x,y) ,满足 a ≤ x ≤ b , c ≤ y ≤ d ,且 gcd(x,y) = k , gcd(x,y) 函数为 x 和 y 的最大公约数。
分析:我们考虑容斥原理分成四段求,那么现在问题变为了求1~n/k,1到m/k内有多少对数(x,y)最大公约数为1了。

f(n,m)=i=1nj=1m[(i,j)==1]=i=1nj=1md|(i,j)u(d)=i=1min(n,m)u(d)ndmd
这样我们可以直接分块, ndmd2(n+m) 个取值,单次查询复杂度为 O(n)
利用莫比乌斯反演我们同样可以得到上述的式子。

#include 
using namespace std;

typedef long long LL;
const int N = 5e4 + 5;
bool vis[N];
int primes[N], miu[N], sum[N];
int init(int n) {
    int tot = 0;
    sum[1] = miu[1] = 1;
    for (int i = 2; i <= n; i++) {
        if (!vis[i]) {
            primes[tot++] = i;
            miu[i] = -1;
        }
        for (int j = 0; j < tot; j++) {
            int k = i * primes[j];
            if (k > n)break;
            vis[k] = true;
            if (i % primes[j])  miu[k] = -miu[i];
            else break;
        }
        sum[i] = sum[i-1] + miu[i];
    }
}
LL solve(int n, int m) {
    if (n > m) swap(n, m);
    LL ans = 0;
    for (int i = 1, last; i <= n; i = last + 1) {
        last = min(n / (n / i), m / (m / i));
        if (sum[last] - sum[i-1]) ans += (LL)(n / i) * (m / i) * (sum[last] - sum[i-1]);
    }
    return ans;
}
int main() {
    init(5e4);
    int t;
    scanf("%d", &t);
    while (t--) {
        int a, b, c, d, k;
        scanf("%d %d %d %d %d", &a, &b, &c, &d, &k);
        a--; c--;
        a /= k; b /= k; c /= k; d /= k;
        printf("%lld\n", solve(b, d) + solve(a, c) - solve(a, d) - solve(c, b));
    }
    return 0;
}

bzoj 2820 YY的GCD
传送门
题意:给定N, M,求1<=x<=N, 1<=y<=M且gcd(x, y)为质数的(x, y)有多少对。
分析:按照上题的套路,我们知道

f(n,m)=pd=1min(np,mp)u(d)ndpmdp
。直接枚举质数然后 O(n) 求解复杂度还是有点高,我们考虑继续化简式子。
f(n,m)=pd=1min(np,mp)u(d)ndpmdp=x=1min(n,m)nxmxp|xu(xp)
,只要对 p|du(dp) 求一个前缀和,这个可以通过对每个质数,枚举其倍数完成,而质数只有 O(nlogn) 个, 之后就可以 O(n) 求解。
这里用到了一个结论: nxy=nxy 。交换求和顺序化简和式,分析复杂度都是解决本题的关键。

#include 
using namespace std;

typedef long long LL;
const int N = 1e7 + 5;
bool vis[N];
int primes[N], miu[N], s[N];

void init(int n) {
    int tot = 0;
    miu[1] = 1;
    for (int i = 2; i <= n; i++) {
        if (!vis[i]) {
            primes[tot++] = i;
            miu[i] = -1;
        }
        for (int j = 0; j < tot; j++) {
            int k = i * primes[j];
            if (k > n)break;
            vis[k] = true;
            if (i % primes[j])  miu[k] = -miu[i];
            else break;
        }
    }
    for (int i = 0; i < tot; i++) {
        for (int j = primes[i], k = 1; j <= n; j += primes[i], k++) s[j] += miu[k];
    }
    for (int i = 1; i <= n; i++) s[i] += s[i-1];
}
int main() {
    init(1e7);
    int T;
    scanf("%d", &T);
    while (T--) {
        LL n, m;
        scanf("%lld %lld", &n, &m);
        if (n > m) swap(n, m);
        LL ans = 0;
        for (int i = 1, last; i <= n; i = last + 1) {
            last = min(n / (n / i), m / (m / i));
            if (s[last] - s[i - 1]) ans += (n / i) * (m / i) * (s[last] - s[i - 1]);
        }
        printf("%lld\n", ans);
    }
    return 0;
}

bzoj 3529 数表
传送门
题意:给q个询问,每个询问给出n,m,a,求 ni=1mj=1F((i,j))[(i,j)<=a] ,F(i)表示i的约数和。
分析:出题人相比上面几题加了一点套路而已-_-。
对于这种带有限制的计数问题,如果不好直接解决,我们可以先直接分析,然后在考虑限制,后面往往可以直接算满足限制的,或者容斥,当然这里与容斥无关。。
先不管a,令ans = \sum_{i=1}^{n}\sum_{j=1}^{m}F((i, j))=\sum_{g=1}^{min(n,m)}F(g) * num(g) ni=1mj=1F((i,j))=min(n,m)g=1F(g)num(g) ,这里num(g)表示有多少对(i,j)使得(i,j)==g。那么可以知道
ans = \sum_{i=1}^{n}\sum_{j=1}^{m}F((i, j))=\sum_{g=1}^{min(n,m)}F(g) * \sum_{i=1}^{min(\frac{n}{g},\frac{m}{g})}u(i) * \frac{n}{gi}*\frac{m}{gi}

ans=i=1nj=1mF((i,j))=g=1min(n,m)F(g)i=1min(ng,mg)u(i)ngimgi

= \sum_{T=1}^{min(n,m)}\frac{n}{T}*\frac{m}{T} * \sum_{g|T}F(g)*u(\frac{T}{g})
=T=1min(n,m)nTmTg|TF(g)u(Tg)

同上题,线性筛处理F函数、莫比乌斯函数以及 \sum_{g|T}F(g)*u(\frac{T}{g}) g|TF(g)u(Tg) 的前缀和,就可以 O(\sqrt n) O(n) 分块搞了。
现在考虑a的限制,我们计算时只能考虑(i,j)<=a的贡献,所以我们可以将F(i)从小到大排序,将a从小到大排序,这样子每次我们只需要对小于等于a的所有F值更新其倍数,所以需要单点修改并且在分块时求前缀和,那么可以考虑使用树状数组维护。
取模是对2的幂取模的,可以使用int计算然后直接相与即可。
本题也算是本蒟蒻做过的为数不多的树状数组了,觉得树状数组在一些带限制的且需要对一个范围内的权值统计和时应用挺多的,单点修改和统计前缀和都是 O(logn) 的。

#include 
using namespace std;

const int N = 100001, mod = 0x7fffffff;
bool vis[N];
int p[N], g[N], miu[N], tmp[N], ans[N];

struct que{
    int n, m, a, id;
    bool operator < (const que& x) const { return a < x.a; }
}q[20005];
struct dsum{
    int s, id;
    bool operator < (const dsum& x) const { return s < x.s; }
}d[N];

int qpow(int x, int n) {
    int res = 1;
    while (n) {
        if (n & 1) res *= x;
        x *= x; n >>= 1;
    }
    return res;
}
void init(int n) {
    miu[1] = 1; int k = 0;
    d[1].id = d[1].s = 1;
    for (int i = 2; i <= n; i++) {
        d[i].id = i;
        if (!vis[i]) p[k++] = i, g[i] = 1, tmp[i] = d[i].s = 1 + i, miu[i] = -1;
        for (int j = 0; j < k; j++) {
            int m = i * p[j];
            if (m > n) break;
            vis[m] = 1;
            if (i % p[j] == 0) {
                g[m] = g[i] + 1;
                tmp[m] = tmp[i] + qpow(p[j], g[m]);
                d[m].s = d[i].s + d[i].s / tmp[i] * (tmp[m] - tmp[i]);
                break;
            }
            miu[m] = -miu[i];
            g[m] = 1; tmp[m] = 1 + p[j];
            d[m].s = d[i].s * tmp[m];
        }
    }
    sort(d + 1, d + N);
}
namespace BIT {
int c[N+100];
inline void add(int x, int y) {
    for (; x < N; x += x & -x) c[x] += y;
}
inline int get(int x) {
    int res = 0;
    for (; x; x -= x & -x) res += c[x];
    return res;
}
}
int main() {
    init(100000);
    int Q;
    scanf("%d" ,&Q);
    for (int i = 0; i < Q; i++) {
        q[i].id = i;
        scanf("%d %d %d", &q[i].n, &q[i].m, &q[i].a);
        if (q[i].n > q[i].m) swap(q[i].n, q[i].m);
    }
    sort(q, q + Q);
    for (int i = 0, cur = 1; i < Q; i++) {
        while (cur < N && d[cur].s <= q[i].a) {
            for (int j = 1; j * d[cur].id < N; j++) if (miu[j]) BIT::add(j * d[cur].id, d[cur].s * miu[j]);
            cur++;
        }
        int res = 0, n = q[i].n, m = q[i].m;
        for (int j = 1, last; j <= n; j = last + 1) {
            last = min(n / (n / j), m / (m / j));
            res += (BIT::get(last) - BIT::get(j - 1)) * (n / j) * (m / j);
        }
        ans[q[i].id] = res & mod;
    }
    for (int i = 0; i < Q; i++) printf("%d\n", ans[i]);
    return 0;
}

bzoj 2693 jzptab
传送门
题意:多组询问,求 ni=1mj=1lcm(i,j)
分析:先将lcm转化为熟悉的gcd,我们有

ans=i=1nj=1mij(i,j)=g=1min(n,m)gF(g)

此处F(g)代表所以有(i,j)==g的i*j之和。那么我们有
F(g)=d=1min(ng,mg)u(d)ddi=1ngdj=1mgdij

sum(x,y)=xi=1yj=1ij ,那么有
ans=g=1min(n,m)gd=1min(ng,mg)u(d)d2sum(ngd,mgd)=T=1min(n,m)sum(nT,mT)g|TTgu(g)g2

同样的套路,我们只需要求出 f(T)=g|TTgu(g)g2 的前缀和,那么就可以 O(n) 计算了。
f(T)是积性函数,可以用线性筛求f(T),当T%p==0,原来T的因子部分变为原来的p倍了,多增加的因子都是有平方因子数,这个都为0,不用考虑。
所以有
f(Tp)={pf(T),f(T)(pp2),if T%p==0;

ps:这个题目的mod不要一眼看成1e9+7了,不然错都不知道在哪。。。

#include 
using namespace std;

typedef long long LL;
const int N = 10000005;
const LL mod = 100000009;
bool vis[N];
int miu[N], p[N];
LL g[N];
int init(int n) {
    int tot = 0;
    miu[1] = 1; g[1] = 1;
    for (int i = 2; i <= n; i++) {
        if (!vis[i]) {
            p[tot++] = i;
            miu[i] = -1;
            if ((g[i] = (i - (LL)i * i % mod)) < 0) g[i] += mod;
        }
        for (int j = 0; j < tot; j++) {
            int k = i * p[j];
            if (k > n) break;
            vis[k] = true;
            if (i % p[j] == 0) { g[k] = p[j] * g[i] % mod; break;}
            LL t = p[j] - (LL)p[j] * p[j] % mod;
            if (t < 0) t += mod;
            g[k] = g[i] * t % mod;
            miu[k] = -miu[i];
        }
    }
    for (int i = 1; i <= n; i++) {
        g[i] += g[i-1];
        if (g[i] >= mod) g[i] -= mod;
    }
}
int main() {
    init(1e7);
    int t;
    scanf("%d", &t);
    while (t--) {
        LL n, m;
        scanf("%lld %lld", &n, &m);
        LL ans = 0;
        if (n > m) swap(n, m);
        for (LL i = 1, last; i <= n; i = last + 1) {
            LL x = n / i, y = m / i;
            last = min(n / x, m / y);
            x = x * (x + 1) / 2 % mod; y = y * (y + 1) / 2 % mod;
            ans = (ans + ((g[last] - g[i-1] + mod) % mod * (x * y % mod)) % mod) % mod;
        }
        printf("%lld\n", ans);
    }
    return 0;
}

hdu 6053 TrickGCD
传送门
题意:给定数组A,求有多少个不同的数组B满足 1BiAi ,且gcd(B_1,B_2,...B_n)>2 gcd(B1,B2,...Bn)>2
分析:相信做完上面的题目之后很容易分析得出ans = \sum_{T=1}^{min(A_1,A_2,...A_n)}-u(T)*\prod_{i=1}^{n}\frac{A_i}{T} ans=min(A1,A2,...An)T=1u(T)ni=1AiT 。多校时这题做到这就不知道怎么优化计算了。。然后各种优化还是超时。。。全世界都会优化的题我不会。。或者这就是弱渣吧。
此处分块没任何意义,我们考虑暴力枚举T,快速计算\prod_{i=1}^{n}\frac{A_i}{T} ni=1AiT ,注意到数据的范围不大,我们可以分段计算,在kT到(k+1)T-1这一段的数除以T都为k,这样子我们只需要统计出cnt[]数组的前缀和,对于每一个T,暴力枚举其倍数的分段,然后快速幂计算即可。总复杂度O(nlog^2n) O(nlog2n)
另外我们也可以通过容斥原理得到上述的式子,此处只需要考虑无平方因子数即可,我们加上以2为公约数的序列个数,加上以3为公约数的序列个数,加上以5为公约数的序列个数….减去以2*3为公约数的序列个数,减去以3*5为公约数的序列个数,减去以2*5为公约数的序列个数…可以发现容斥的系数刚好是莫比乌斯函数值的相反数。

#include 
using namespace std;

typedef long long LL;
const int N = 1e5 + 5;
const LL mod = 1e9 + 7;
bool vis[N];
int prime[N], miu[N], a[N], cnt[N<<1];

void init(int n) {
    miu[1] = -1;
    for (int i = 1; i <= n; i++) if (miu[i]) {
        for (int j = i << 1; j <= n; j += i) miu[j] -= miu[i];
    }
}
LL qpow(LL x, LL a) {
    LL res = 1;
    while (a) {
        if (a & 1) res = res * x % mod;
        x = x * x % mod;
        a >>= 1;
    }
    return res;
}
int main() {
    init(1e5);
    int t, ca = 0;
    scanf("%d", &t);
    while (t--) {
        int n, mi = 100000;
        scanf("%d", &n);
        for (int i = 0; i <= 200000; i++) cnt[i] = 0;
        while (n--) {
            int x;
            scanf("%d", &x);
            cnt[x]++;
            mi = min(mi, x);
        }
        LL ans = 0;
        for (int i = 1; i <= 200000; i++) cnt[i] += cnt[i - 1];
        for (int i = 2; i <= mi; i++) if (miu[i]) {
            LL s = 1;
            for (int j = i << 1, k = 2; j <= 100000; j += i, k++) s = s * qpow(k, cnt[j + i - 1] - cnt[j - 1]) % mod;
            ans = (ans + s * miu[i] + mod) % mod;
        }
        printf("Case #%d: %lld\n", ++ca, ans);
    }
    return 0;
}

hdu 5382
传送门
题意:定义F(n)=\sum_{i=1}^{n}\sum_{j=1}^{n}[lcm(i,j)+gcd(i,j)>=n] F(n)=ni=1nj=1[lcm(i,j)+gcd(i,j)>=n] ,求S(n)=\sum_{i=1}^{n}F(i) S(n)=ni=1F(i)
分析:考虑F(n) F(n) ,这个大于等于不太好考虑,我们不妨看看等于的情况。
f(n)=\sum_{i=1}^{n}\sum_{j=1}^{n}[lcm(i,j)+gcd(i,j)==n] f(n)=ni=1nj=1[lcm(i,j)+gcd(i,j)==n] ,那么有
f(n)=\sum_{i=1}^{n}\sum_{j=1}^{n}[\frac{i*j}{gcd(i,j)}+gcd(i,j)==n]=\sum_{g=1}^{n}\sum_{i=1}^{\frac{n}{g}}\sum_{j=1}^{\frac{n}{g}}[(i,j)==1]*[g(ij+1)==n] =\sum_{g|n}\sum_{i=1}^{\frac{n}{g}}\sum_{j=1}^{\frac{n}{g}}[i*j==\frac{n}{g}-1][(i,j)==1]=\sum_{g|n}2^{G(\frac{n}{g}-1)}

f(n)=i=1nj=1n[ijgcd(i,j)+gcd(i,j)==n]=g=1ni=1ngj=1ng[(i,j)==1][g(ij+1)==n]=g|ni=1ngj=1ng[ij==ng1][(i,j)==1]=g|n2G(ng1)
,这里G(n)表示n的不同的质因子的个数。
所以,我们可以线性筛 G(n) G(n) ,然后 O(nlogn) O(nlogn) 更新 f(n) f(n)
既然已经知道了 f(n) f(n) ,那么我们可以考虑容斥计算 F(n) F(n) ,有
F(n)=i=1nj=1n[lcm(i,j)+gcd(i,j)>=n]=
i=1n1j=1n1[lcm(i,j)+gcd(i,j)>=n1]+(2n1)f(n1)

那么可以得到F的递推式, F(n)=F(n1)f(n1)+2n1
预处理F以及S即可。

#include 
using namespace std;

typedef long long LL;
const int N = 1e6 + 5, mod = 258280327;

int f[N], g[N], h[N], pri[N], p[20];
bool vis[N];
void init(int n) {
    g[1] = p[0] = 1;
    for (int i = 1; i <= 20; i++) p[i] = p[i-1] << 1;
    int k = 0;
    for (int i = 2; i <= n; i++) {
        if (!vis[i]) pri[k++] = i, g[i] = 1;
        for (int j = 0; j < k; j++) {
            int s = i * pri[j];
            if (s > n) break;
            vis[s] = 1;
            if (i % pri[j] == 0) { g[s] = g[i]; break; }
            g[s] = g[i] + 1;
        }
        g[i] = p[g[i]];
    }
    for (int i = 1; i <= n; i++) {
        for (int j = i << 1, k = 2; j <= n; j += i, k++) {
            h[j] += g[k-1];
            if (h[j] >= mod) h[j] -= mod;
        }
    }
    for (int i = 1; i <= n; i++) {
        f[i] = f[i-1] + 2 * i - 1 - h[i-1];
        if (f[i] >= mod) f[i] -= mod;
    }
    for (int i = 1; i <= n; i++) {
        f[i] += f[i-1];
        if (f[i] >= mod) f[i] -= mod;
    }
}
int main() {
    init(1e6);
    int t;
    scanf("%d", &t);
    while (t--) {
        int n;
        scanf("%d", &n);
        printf("%d\n", f[n]);
    }
    return 0;
}

hdu 5072 Coprime
传送门
题意:给定n个不同的数,求多少个a,b,c满足两两互质或者两两不互质。
分析:千万别看成gcd相等了。。。明显地,从正面考虑是不太现实的,情况数及其多,我们考虑从反面出发,C(n,3)减去不合法的数目即可。
如果一个三元组abc不合法,那么必定存在一个数与其中一个数互质,而与另外一个数不互质,那么我们枚举每一个数a[i],算有多少个数与它互质,有多少个数与它不互质,假设有s个数与a[i]互质,那么a[i]对答案的贡献是s*(n-1-s),注意到这样计算会有重复,比如说7 15 10符合条件,此时7与10互质,但是7 10 15同样符合条件,如果7 15 21符合条件,此时7与21不互质但是15 7 21同样符合条件,所以最后需要除以2去除重复的情况。
算互质的个数可以通过容斥做,之前也有提到过即ans = d|xu(d)num(d) ,num(d)表示d的倍数的个数,所以我们只需要O(nlogn)算倍数个数,O(nlogn)更新,O(n)求解不合法的数目即可。

#include 
using namespace std;

typedef long long LL;
const int N = 1e5 + 5;
int num[N], miu[N], pri[N], cnt[N], a[N];
bool vis[N];

void init() {
    int k = 0;
    miu[1] = 1;
    for (int i = 2; i <= 100000; i++) {
        if (!vis[i]) pri[k++] = i, miu[i] = -1;
        for (int j = 0; j < k; j++) {
            int s = i * pri[j];
            if (s > 100000) break;
            vis[s] = 1;
            if (i % pri[j] == 0) break;
            miu[s] = -miu[i];
        }
    }
}
int main() {
    init();
    int t, ca = 0;
    scanf("%d", &t);
    while (t--) {
        int n, ma = 0;
        scanf("%d", &n);
        memset(vis, 0, sizeof(vis));
        for (int i = 0; i < n; i++) {
            scanf("%d", a + i); vis[a[i]] = 1;
            ma = max(ma, a[i]);
        }
        for (int i = 1; i <= ma; i++) if (miu[i]) {
            for (int j = i; j <= ma; j += i) if (vis[j]) num[i]++;
            num[i] *= miu[i];
            for (int j = i; j <= ma; j += i) cnt[j] += num[i];
        }
        cnt[1]--;
        LL ans = (LL)n * (n - 1) * (n - 2) / 6, s = 0;
        for (int i = 0; i < n; i++) s += (LL)cnt[a[i]] * (n - 1 - cnt[a[i]]);
        printf("%lld\n", ans - s / 2);
        if (t) {
            memset(num + 1, 0, ma << 2);
            memset(cnt + 1, 0, ma << 2);
        }
    }
    return 0;
}

hdu 6102 GCDispower
传送门
题意:给定1~n(n <= 100000)的一个排列,定义区间[L, R]的power值为

i=LRj=i+1Rk=j+1R(gcd(p[i],p[j])==p[k])p[k]
,有m(m<=100000)个查询,每次给出L,R,求该区间的poewr值。
分析:本题应该是属于比较好的题了!问题等价于求解
k=L+2Rp[k]i=Lk2j=i+1k1(gcd(p[i],p[j])==p[k])
,所以说对于每一个k,我们需要求解在区间内有多少组(i,j)使得 gcd(p[i],p[j])==p[k] ,那么我们首先可以知道能产生贡献的必定是位置在k左边的倍数对,而且对于每一个倍数p[i],它能贡献左端点在1到i之间的区间,产生的贡献是i右边与p[i]最大公约数为p[k]的对的数量,这个可以在素因子的组合之间做容斥求出。所以我们可以从右往左枚举每一个倍数,并且更新该位置产生的贡献,这个可以使用树状数组维护。更新的时候选取的总是最左边的那个满足要求的位置,注意到是求前缀和,所以更新地时候还要考虑到这个问题,只需要在后面这样,查询时是 O(logn) 的。
预处理一个数的所有素因子组合,然后从右往左枚举每一个数的倍数,然后维护和更新产生的贡献。考虑到查询数比较多,全部离线处理一次,复杂度是 O(nlog2n) 的,单组查询 O(1)
ps:本题值得好好总结,感觉方法很好。

#include 
using namespace std;

typedef long long LL;
const int N = 1e5 + 5;
struct po{
    int f, s;
    po(int f = 1, int s = 1) : f(f), s(s) {}
};
void read(int& res) {
    char c;
    while (!(isdigit(c = getchar())));
    res = c - '0';
    while (isdigit(c = getchar())) res = res * 10 + c - '0';
}
vector q[N], d[N];
vector<int> f[N];
LL c[N], ans[N];
int pos[N], v[N], n, m, a[N], cnt[N];

int lowbit(int x) {
    return x & -x;
}
LL get(int x) {
    LL res = 0;
    while (x) { res += c[x]; x -= lowbit(x); }
    return res;
}
void add(int x, LL va) {
    int t = x;
    while (x <= n) { c[x] += va; x += lowbit(x); }
}
int getnum(int x) {
    int res = 0;
    for (int i = 0; i < d[x].size(); i++) res += cnt[d[x][i].f] * d[x][i].s;
    return res;
}
void upd(int x, bool v) {
    for (int i = 0; i < d[x].size(); i++) v ? ++cnt[d[x][i].f] : --cnt[d[x][i].f];
}
void init(int n) {
    for (int i = 2; i <= n; i++) if (!v[i]) {
        for (int j = i; j <= n; j += i) {
            v[j] = 1;
            f[j].push_back(i);
        }
    }
    for (int i = 2; i <= n; i++) {
        int s = 1 << f[i].size();
        for (int j = 0; j < s; j++) {
            d[i].push_back(po());
            for (int k = 0; k < f[i].size(); k++) {
                if (j >> k & 1) d[i][j].f *= f[i][k], d[i][j].s *= -1;
            }
        }
    }
}
void solve() {
    for (int i = 1; i <= n; i++) {
        int k = 1;
        for (int j = a[i] << 1; j <= n; j += a[i]) {
            if (pos[j] < i) v[k++] = pos[j];
        }
        sort(v + 1, v + k);
        LL sum = 0;
        for (int j = k - 1; j >= 1; j--) {
            int va = a[v[j]] / a[i];
            sum += (LL)getnum(va) * a[i];
            add(v[j-1] + 1, sum); add(v[j] + 1, -sum);
            upd(va, 1);
        }
        for (int j = k - 1; j >= 1; j--) upd(a[v[j]] / a[i], 0);
        for (po j : q[i]) ans[j.f] = get(j.s);
    }
}
int main() {
    init(100000);
    int t;
    read(t);
    while (t--) {
        read(n); read(m);
        for (int i = 1; i <= n; i++) {
            read(a[i]); c[i] = 0;
            q[i].clear(); pos[a[i]] = i;
        }
        for (int i = 1; i <= m; i++) {
            int l, r;
            read(l); read(r);
            q[r].push_back(po(i, l));
        }
        solve();
        for (int i = 1; i <= m; i++) printf("%lld\n", ans[i]);
    }
    return 0;
}

hdu 6134 Battlestation Operational
传送门
题意:计算 f(n)=ni=1ij=1ij[(i,j)==1]
分析:这是一个明显的前缀和形式,计算 g(i)=ij=1ij[(i,j)==1]
g(i)=φ(i)1+ij=1ijd|(i,j)u(d)=φ(i)1+d|iu(d)idj=1ijd
到了这里,就能够看出来明显的套路了。线性筛因子个数,然后对于每一个数直接枚举其倍数算贡献,最后统计前缀和即可。查询O(1),复杂度 O(nlogn)

#include 
using namespace std;

typedef long long LL;
const int N = 1e6 + 5, mod = 1e9 + 7;
bool vis[N];
int prime[N], phi[N], g[N];
short miu[N];
LL sum[N], f[N];

void init(int n) {
    phi[1] = sum[1] = miu[1] = 1;
    int k = 0;
    for (int i = 2; i <= n; i++) {
        if (!vis[i]) {
            prime[k++] = i;
            g[i] = 1; phi[i] = i - 1;
            sum[i] = 2; miu[i] = -1;
        }
        for (int j = 0; j < k; j++) {
            int s = i * prime[j];
            if (s > n) break;
            vis[s] = 1;
            if (i % prime[j] == 0) {
                g[s] = g[i] + 1;
                sum[s] = sum[i] + sum[i] / g[s];
                phi[s] = phi[i] * prime[j];
                break;
            }
            g[s] = 1; sum[s] = sum[i] << 1;
            phi[s] = phi[i] * (prime[j] - 1);
            miu[s] = -miu[i];
        }
    }
    for (int i = 2; i <= n; i++) sum[i] += sum[i-1];
    for (int i = 1; i <= n; i++) {
        if (miu[i]) for (int j = i; j <= n; j += i) f[j] = (f[j] + miu[i] * sum[j/i]) % mod;
        if (f[i] < 0) f[i] += mod;
    }
    for (int i = 1; i <= n; i++) f[i] = (f[i] + phi[i] - 1 + f[i-1]) % mod;
}
int main() {
    init(1e6);
    int n;
    while (~scanf("%d", &n)) printf("%lld\n", f[n]);
    return 0;
}

接下来来看一些积性函数的问题。
hdu 5628 Clarke and math
传送门
题意:给定f(i) f(i) 序列, 1<=i<=n ,求 g(i)=i1|ii2|i1i3|i2...ik|ik1f(ik) , 1<=i<=n
分析: 这里我们可以看出来 g 函数是 f 函数与k个恒等函数 I 的狄利克雷卷积,而狄利克雷卷积满足交换律和结合律,所以我们可以知道 g(n)=(fIk)(n) ,这样我们只需要快速幂计算k个恒等函数的卷积,然后最后在做一次与f函数的卷积就够了,复杂度 O(nlognlogk) 。初始化为元函数 e(n) 即可。

#include 
using namespace std;

typedef long long LL;
const int N = 1e5 + 5, mod = 1e9 + 7;
LL ans[N], f[N], g[N], h[N];
int n, k;

void calc(LL* f, LL* g) {
    memset(h + 1, 0, n << 3);
    for (int i = 1; i * i <= n; i++) {
        h[i * i] += f[i] * g[i] % mod;
        if (h[i * i] >= mod) h[i * i] -= mod;
        for (int j = i + 1; i * j <= n; j++) {
            h[i * j] += f[i] * g[j] % mod + f[j] * g[i] % mod;
            while (h[i * j] >= mod) h[i * j] -= mod;
        }
    }
    copy(h + 1, h + 1 + n, g + 1);
}
int main() {
    int t;
    scanf("%d", &t);
    while (t--) {
        scanf("%d %d", &n, &k);
        for (int i = 1; i <= n; i++) scanf("%lld", f + i), ans[i] = 0, g[i] = 1;
        ans[1] = 1;
        while (k) {
            if (k & 1) calc(g, ans);
            calc(g, g); k >>= 1;
        }
        calc(f, ans);
        for (int i = 1; i < n; i++) printf("%lld ", ans[i]);
        printf("%lld\n", ans[n]);
    }
    return 0;
}

hdu 5970 最大公约数
传送门
题意:定义函数f如下:
void f(int x, int y) {
int c = 0;
while (y) {
c++;
int t = x % y;
x = y;
y = t;
}
}
return c * x * x;
给定 n<=666666666,m<=666,p<=666666666 ,求 ni=1mj=1ijf(i,j)
分析:m的范围很小,这个必定是关键点。
分析f函数可以知道,对于 x=i(mody) 的所有x, f(x,y) 函数返回的值都是相同的。我们可以得到

ans=i=1mj=1ik=0j+ik<=ni(j+ik)(i,j)(i,j)c(j,i)

到了这里,似乎没办法直接搞,但是考虑到k的范围可以进一步根据c(i,j)来缩小。
那么我们得到
ans=i=1mj=1ik=0c(i,j)1l=0j+i(k+c(i,j)l)<=ni(j+i(k+c(i,j)l))(i,j)(i,j)c(i,j)=i=1mj=1ik=0c(i,j)1l=0j+i(k+c(i,j)l)<=n(i(j+ik)(i,j)(i,j)c(i,j)+lii(i,j)(i,j))

到了这里,我们只需要枚举 i,j,k ,然后计算等差数列的和了。复杂度 O(m2log(m))

#include 
using namespace std;

typedef long long LL;
const int N = 670;
int c[N][N], gcd[N][N];

void init(int n) {
    gcd[1][1] = c[1][1] = 1;
    for (int i = 2; i <= n; i++) {
        gcd[i][i] = i; c[1][i] = 2;
        gcd[i][1] = gcd[1][i] = c[i][i] = c[i][1] = 1;
        for (int j = 2; j < i; j++) {
            if (!gcd[i][j]) gcd[i][j] = gcd[j][i-j];
            gcd[j][i] = gcd[i][j];
            c[i][j] = c[j][i % j] + 1;
            c[j][i] = c[i][j] + 1;
        }
    }
}

int main() {
    //freopen("in.txt", "r", stdin);
    //freopen("out.txt", "w", stdout);
    init(666);
    int t;
    scanf("%d", &t);
    while (t--) {
        int n, m, p;
        scanf("%d %d %d", &n, &m, &p);
        int ans = 0;
        for (int j = 1; j <= m; j++) {
            for (int i = 1; i <= j && i <= n; i++) {
                int _c = c[i][j], g = gcd[i][j] * gcd[i][j];
                for (int k = 0; k < _c; k++) {
                    if (i + k * j > n) break;
                    LL tm = (n - (i + j * k)) / (_c * j) + 1;
                    ans += (LL)(i + j * k) * j / (_c * g) * tm % p;
                    if (ans >= p) ans -= p;
                    ans += (tm - 1) * tm / 2 % p * (j * j / g) % p;
                    if (ans >= p) ans -= p;

                }
            }
        }
        printf("%d\n", ans);
    }
    return 0;
}

hdu 5528 Count a * b
传送门
题意: f(m)=m1i=0m1j=0[ijmodm!=0] ,求 g(n)=m|nf(m)
分析:求i*j % mod m != 0不太方便,我们从反面考虑转化为熟悉的问题。
那么答案就是ans = m|nm2m1i=0m1j=0[ijmodm==0] ,这里我们令 f(m)=m1i=0m1j=0[ijmodm==0] ,那么

f(m)=i=0m1mm(i,m)=d|mdφ(md)
.
所以
m|nd|mdφ(md)=d|ndk|ndφ(k)=d|ndnd=d(n)n

因子个数是积性函数,那么现在令 F(n)=d|nd2 。很容易可以看出这一个积性函数,那么我们只需要算 F(pe)=1+p2+p4+...+p2e=p2e+21p21
到了这里,我们只需要对n做质因数分解,然后直接算即可。对 264 的取模可以用unsigned long long。我们可以预处理一下素数,这样子质因数分解的复杂度就是最多 O(nlogn) 的。

#include 
using namespace std;

typedef unsigned long long LL;
const int N = 31630;
bool vis[N];
int prime[N], k;
void init(int n) {
    for (int i = 2; i <= n; i++) {
        if (!vis[i]) prime[k++] = i;
        for (int j = 0; j < k; j++) {
            int s = i * prime[j];
            if (s > n) break;
            vis[s] = true;
            if (i % prime[j] == 0) break;
        }
    }
    prime[k] = 40000;
}
LL solve(int n) {
    int m = n;
    LL all = 1, sum = 1;
    for (int i = 0; prime[i] * prime[i] <= n; i++) {
        int p = prime[i];
        if (n % p == 0) {
            int cnt = 0;
            while (n % p == 0) n /= p, cnt++;
            LL f = 1, s = 1;
            for (int j = 0; j < cnt; j++) s *= p, f += s * s;
            all *= f;
            sum *= (cnt + 1);
        }
    }
    if (n != 1) all *= (LL)n * n + 1, sum <<= 1;
    return all - m * sum;
}
int main() {
    //freopen("in.txt", "r", stdin);
    //freopen("out1.txt", "w", stdout);
    init(31625);
    int t;
    scanf("%d", &t);
    while (t--) {
        int n;
        scanf("%d", &n);
        printf("%llu\n", solve(n));
    }
    return 0;
}

看到上面那题,忍不住要把一个相似的题拿出来-_-。
csu 1934 计数
传送门
题意:给定n,m,a,求满足x*y%a==0且1<=x<=n,1<=y<=m的(x,y)的对数,n<=1e18,m<=1e18,a<=1e9。
分析:似乎看起来与上题有点相似,但是注意到这里x和y是可以大于a的,所以没办法搞,那么我们从一般的思路考虑。我们只需要枚举a的因子,然后直接算即可,也就是说ans = d|andmda 。注意到这里明显是有重复的,所以我们考虑容斥。
对a做质因数分解,假设 a=ti=1peii 。算出 x=pf11pf22...pftt(0<=fi<=ei) 的每一种形式下满足条件的对数,这样子就不会有重复了,在算总共 ti=1(ei+1) 每种x下时的数目时如果x关于a的某个质因数 pi 的指数 fi 小于 ei ,那么这时我们就需要容斥减掉这个指数为 fi+1 的情况了~。
似乎说的一点也不清楚?

#include 
using namespace std;

typedef long long LL;
const LL mod = 333333333333333331LL;
int pri[31625], fac[13][2], k, s[13], p[13][30], lef[15], val[15];
bool vis[31625];
LL a, b, c, ans, t;

LL mulmod(LL x, LL y) {
    if (x >= mod) x %= mod;
    if (y >= mod) y %= mod;
    LL tmp = (x * y - (LL)((long double)x / mod * y + 1e-8) * mod) % mod;
    return tmp < 0 ? tmp + mod : tmp;
}
void init(int n) {
    for (int i = 0; i <= 11; i++) lef[i] = 1 << i;
    for (int i = 2; i <= n; i++) if (!vis[i]) {
        pri[k++] = i;
        for (int j = i * i; j <= n; j += i) vis[j] = 1;
    } pri[k++] = 31650;
}
void dfs(int i, LL mul, int* s) {
    if (i == k) {
        int l = 0;
        for (int j = 0; j < k; j++) if (s[j] < fac[j][1]) val[l++] = fac[j][0];
        LL sum = b / mul;
        for (int j = 1; j < lef[l]; j++) {
            LL tmp = mul; bool f = 0;
            for (int _i = 0; _i < l; _i++) if (j & lef[_i]) f = !f, tmp *= val[_i];
            sum += f ? -(b / tmp) : b / tmp;
        }
        ans += mulmod(sum, c / (t / mul));
        if (ans >= mod) ans -= mod;
        return ;
    }
    for (int j = 0; j <= fac[i][1]; j++) {
        s[i] = j;
        dfs(i + 1, mul * p[i][j], s);
    }
}
int main() {
    init(31623);
    while (~scanf("%lld %lld %lld", &a, &b, &c)) {
        if (b < a / c) { puts("0"); continue; }
        ans = k = 0; t = a;
        for (int i = 0, j = pri[0]; a != 1 && j * j <= t; j = pri[++i]) {
            if (a % j == 0) {
                int cnt = 0; p[k][0] = 1;
                while (a % j == 0) a /= j, ++cnt, p[k][cnt] = p[k][cnt-1] * j;
                fac[k][0] = j, fac[k++][1] = cnt;
            }
        }
        if (a > 1) p[k][1] = fac[k][0] = a, p[k][0] = fac[k++][1] = 1;
        dfs(0, 1, s);
        printf("%lld\n", ans);
    }
    return 0;
}

51nod 1244 莫比乌斯函数之和
传送门

题意:求 bi=au(i)
分析:令 s(n)=ni=1u(i) ,那么

s(n)=i=1n([i==1]d|i,d<iu(d))=1j=2ns(nj)
.直接杜教筛即可。

#include 
using namespace std;

typedef long long LL;
const int N = 1e7 + 5;
unordered_mapint> mp;
bool vis[N];
int prime[N], miu[N];

void init(int n) {
    miu[1] = 1;
    int k = 0;
    for (int i = 2; i <= n; i++) {
        if (!vis[i]) prime[k++] = i, miu[i] = -1;
        for (int j = 0; j < k; j++) {
            int s = i * prime[j];
            if (s > n) break;
            vis[s] = 1;
            if (i % prime[j] == 0) { break; }
            else miu[s] = -miu[i];
        }
    }
    for (int i = 2; i <= n; i++) miu[i] += miu[i-1];
}
LL d(LL n) {
    if (n <= 10000000LL) return miu[n];
    if (mp[n]) return mp[n];
    int res = 1;
    for (LL i = 2, last; i <= n; i = last + 1) {
        last = n / (n / i);
        res -= (last - i + 1) * d(n / i);
    }
    return mp[n] = res;
}
int main() {
    init(1e7);
    LL a, b;
    while (~scanf("%lld %lld", &a, &b)) printf("%d\n", d(b) - d(a - 1));
    return 0;
}

51nod 1244 欧拉函数之和
传送门
题意:求 ni=1φ(i)
分析:令 s(n)=ni=1φ(i) ,那么

s(n)=i=1n(id|i,d<iφ(d))=i=1nij=2ns(nj)
.
直接杜教筛。

#include 
using namespace std;

typedef long long LL;
const LL mod = 1e9 + 7, inv = 500000004LL;
const int N = 1e7 + 5;
unordered_map mp;
bool vis[N];
int prime[N], phi[N];

void init(int n) {
    phi[1] = 1;
    int k = 0;
    for (int i = 2; i <= n; i++) {
        if (!vis[i]) prime[k++] = i, phi[i] = i - 1;
        for (int j = 0; j < k; j++) {
            int s = i * prime[j];
            if (s > n) break;
            vis[s] = 1;
            if (i % prime[j] == 0) {
                phi[s] = phi[i] * prime[j];
                break;
            }
            else phi[s] = phi[i] * (prime[j] - 1);
        }
    }
    for (int i = 2; i <= n; i++) phi[i] = (phi[i] + phi[i-1]) % mod;
}
LL d(LL n) {
    if (n <= 10000000LL) return phi[n];
    if (mp[n]) return mp[n];
    LL res = (n % mod * ((n + 1) % mod)) % mod * inv % mod;
    for (LL i = 2, last; i <= n; i = last + 1) {
        last = n / (n / i);
        res = (res - (last - i + 1) * d(n / i) % mod + mod) % mod;
    }
    return mp[n] = res;
}
int main() {
    init(1e7);
    LL n;
    while (~scanf("%lld", &n)) printf("%lld\n", d(n));
    return 0;
}

hdu 5608 function
传送门
题意:求 ni=1f(i) ,且 N23N+2=d|Nf(d)
分析:最基础的杜教筛,和上面两题一样,拿来构造狄利克雷卷积的函数g(n)都是恒等函数I(n)=1。

#include 
using namespace std;

typedef long long LL;
const LL mod = 1e9 + 7, inv1 = 166666668LL, inv2 = 500000004LL;
const int N = 1e6 + 5;
LL f[N];
unordered_map mp;
void init(int n) {
    f[1] = 0;
    for (int i = 2; i <= n; i++) {
        f[i] = ((LL)i * i % mod - 3 * i + mod) % mod + 2;
        if (f[i] >= mod) f[i] -= mod;
    }
    int up = n >> 1;
    for (int i = 2; i <= up; i++) {
        for (int j = i << 1; j <= n; j += i) f[j] = (f[j] - f[i] + mod) % mod;
    }
    for (int i = 2; i <= n; i++) f[i] = (f[i] + f[i-1]) % mod;
}
LL get(LL n) {
    if (n <= 1000000LL) return f[n];
    if (mp.count(n)) return mp[n];
    LL res = n * (n + 1) % mod * (n << 1 | 1) % mod * inv1 % mod;
    res = (res - 3 * (n * (n + 1) % mod * inv2 % mod) % mod + mod) % mod;
    res = (res + 2 * n) % mod;
    for (LL i = 2, last; i <= n; i = last + 1) {
        last = n / (n / i);
        res = (res - (last - i + 1) * get(n / i) % mod + mod) % mod;
    }
    return mp[n] = res;
}
int main() {
    init(1e6);
    int t;
    scanf("%d", &t);
    while (t--) {
        int n;
        scanf("%d", &n);
        printf("%lld\n", get(n));
    }
    return  0;
}

51nod 1238 最小公倍数之和
传送门
题意:求 ni=1nj=1lcm(i,j),n<=1e10
分析:ans = ni=1nj=1ij(i,j)=ng=1gngi=1ngj=1ij[(i,j)==1]
f(n)=ni=1nj=1ij[(i,j)==1]
可以知道

f(n)=1+2i=2nj=1iij[(i,j)==1]=i=1ni2φ(i)

那么
f(n)=i=1ni2(id|i,d<iφ(d))=i=1i3i=1nd|i,d<iφ(d)
=i=1i3d=1nφ(d)j=2nd(jd)2=i=1i3d=1nφ(d)d2j=2ndj2
=i=1i3j=2nj2f(nj)

化简到此处,我们再次回到熟悉的形式,只需要分段枚举g,然后杜教筛算f函数即可,这里外面多了一层g的分块,但是复杂度仍然是 O(n23) 的。

51nod 1238 最大公约数之和
传送门
题意:求 ni=1nj=1gcd(i,j),n<=1e10
分析:相比上题,本题容易多了。。

ans=i=1nj=1ngcd(i,j)=i=1nj=1nd|i,d|jφ(d)=d=1nφ(d)(nd)2

直接套杜教筛即可。

#include 
using namespace std;

typedef long long LL;
const LL mod = 1e9 + 7, inv = 500000004LL;
const int N = 1e7 + 5;
unordered_map mp;
bool vis[N];
int prime[N], phi[N];

void init(int n) {
    phi[1] = 1;
    int k = 0;
    for (int i = 2; i <= n; i++) {
        if (!vis[i]) prime[k++] = i, phi[i] = i - 1;
        for (int j = 0; j < k; j++) {
            int s = i * prime[j];
            if (s > n) break;
            vis[s] = 1;
            if (i % prime[j] == 0) {
                phi[s] = phi[i] * prime[j];
                break;
            }
            else phi[s] = phi[i] * (prime[j] - 1);
        }
    }
    for (int i = 2; i <= n; i++) phi[i] = (phi[i] + phi[i-1]) % mod;
}
LL d(LL n) {
    if (n <= 10000000LL) return phi[n];
    if (mp.count(n)) return mp[n];
    LL res = (n % mod * ((n + 1) % mod)) % mod * inv % mod;
    for (LL i = 2, last; i <= n; i = last + 1) {
        last = n / (n / i);
        res = (res - (last - i + 1) * d(n / i) % mod + mod) % mod;
    }
    return mp[n] = res;
}
int main() {
    init(1e7);
    LL n;
    scanf("%lld", &n);
    LL res = 0;
    for (LL i = 1, last; i <= n; i = last + 1) {
        LL x = n / i; last = n / x; x %= mod; x = x * x % mod;
        res = (res + (d(last) - d(i - 1) + mod) % mod * x % mod) % mod;
    }
    printf("%lld\n", res);
    return 0;
}

51nod 1227 平均最小公倍数
传送门
题意:定义 A(n)=n

你可能感兴趣的:(总结心得,数学)