数论专题(莫反)题单&题解

终于写完13题的题解了
用时 23 23 23

以此帖纪念洛谷红名

题单:

题单 \Huge \color{red}{题单} 题单

T1 P2568 GCD

题目描述

给定正整数 n n n,求 1 ≤ x , y ≤ n 1\le x,y\le n 1x,yn gcd ⁡ ( x , y ) \gcd(x,y) gcd(x,y) 为素数的数对 ( x , y ) (x,y) (x,y) 有多少对。

题目简化:求 ∑ p ∈ p r i m e s ∑ i = 1 n ∑ j = 1 n [ gcd ⁡ ( i , j ) = = p ] \sum_{p \in primes} \sum_{i=1}^{n} \sum_{j=1}^{n} [\gcd(i, j) == p] pprimesi=1nj=1n[gcd(i,j)==p]

转化为 ∑ p ∈ p r i m e s ∑ i = 1 ⌊ n p ⌋ ∑ j = 1 ⌊ n p ⌋ [ gcd ⁡ ( i , j ) = = 1 ] \sum_{p \in primes} \sum_{i=1}^{\lfloor \frac{n}{p} \rfloor} \sum_{j=1}^{\lfloor \frac{n}{p} \rfloor} [\gcd(i, j) == 1] pprimesi=1pnj=1pn[gcd(i,j)==1]

a n s = ∑ i = 1 ⌊ n p ⌋ ϕ ( i ) ans = \sum_{i=1}^{\lfloor \frac{n}{p} \rfloor} \phi(i) ans=i=1pnϕ(i)

#include <bits/stdc++.h>
using namespace std;
const long long N = 1e7 + 10;
long long euler[N], primes[N], sum[N], cnt;
bool st[N];
long long n;

void get_eulers(long long n)  // 线性筛法求1~n的欧拉函数
{
    euler[1] = 1;
    for (long long i = 2; i <= n; i ++ ) {
        if (!st[i]) {
            primes[ ++cnt ] = i;
            euler[i] = i - 1;
        }
        for (long long j = 1; primes[j] <= n / i; j ++ ) {
            long long t = primes[j] * i;
            st[t] = true;
            if (i % primes[j] == 0) {
                euler[t] = euler[i] * primes[j];
                break;
            }
            euler[t] = euler[i] * (primes[j] - 1);
        }
    }
}

int main() {
    scanf("%d", &n);
    get_eulers(n);
    for (long long i = 1; i <= n; i++) sum[i] = sum[i - 1] + euler[i];
    long long ans = 0;
    for (long long i = 1; i <= cnt && primes[i] <= n; i++) ans += 1ll * (sum[n / primes[i]] * 2) - 1;
    cout << ans;
    return 0;
}

T2 P2398 GCD SUM

题目描述

∑ i = 1 n ∑ j = 1 n gcd ⁡ ( i , j ) \sum_{i=1}^n \sum_{j=1}^n \gcd(i, j) i=1nj=1ngcd(i,j)

gcd ⁡ \gcd gcd 入手。

f k f_k fk gcd ⁡ ( i , j ) = = k \gcd(i,j) == k gcd(i,j)==k 的个数。

a n s = ∑ i = 1 n f i × i ans = \sum_{i=1}^{n} f_i \times i ans=i=1nfi×i

但这样不好算,因此引入 g g g 数组。

g k g_k gk k ∣ g c d ( i , j ) k | gcd(i,j) kgcd(i,j) 的个数。

g g g 的定义, g k = ⌊ n k ⌋ 2 g_k = \lfloor \frac{n}{k} \rfloor ^ 2 gk=kn2

这两个 g k g_k gk 相等,因此可以这么表示 f k f_k fk

f k = ⌊ n k ⌋ 2 − f 2 k − f 3 k − . . . . . . f_k = \lfloor \frac{n}{k} \rfloor ^ 2 - f_{2k} - f{3k} - ...... fk=kn2f2kf3k......

f f f 数组方便多了,可以逆序推导 f f f,最后统计答案。

#include <bits/stdc++.h>
using namespace std;
const long long N = 1e5 + 10;
long long n, f[N];
long long ans = 0;
int main() {
    scanf("%d", &n);
    for (long long i = n; i > 0; i--) {
        long long x = i; f[i] = (n / i) * (n / i);
        while (x + i <= n) {
            x += i;
            f[i] -= f[x];
        }
        ans += f[i] * i;
    }
    cout << ans;
    return 0;
}

T3 P1447 [NOI2010] 能量采集

题目描述

栋栋有一块长方形的地,他在地上种了一种能量植物,这种植物可以采集太阳光的能量。在这些植物采集能量后,栋栋再使用一个能量汇集机器把这些植物采集到的能量汇集到一起。

栋栋的植物种得非常整齐,一共有 n n n 列,每列有 m m m 棵,植物的横竖间距都一样,因此对于每一棵植物,栋栋可以用一个坐标 ( x , y ) (x, y) (x,y) 来表示,其中 x x x 的范围是 1 1 1 n n n y y y 的范围是 1 1 1 m m m,表示是在第 x x x 列的第 y y y 棵。

由于能量汇集机器较大,不便移动,栋栋将它放在了一个角上,坐标正好是 ( 0 , 0 ) (0, 0) (0,0)

能量汇集机器在汇集的过程中有一定的能量损失。如果一棵植物与能量汇集机器连接而成的线段上有 k k k 棵植物,则能量的损失为 2 k + 1 2k + 1 2k+1。例如,当能量汇集机器收集坐标为 ( 2 , 4 ) (2, 4) (2,4) 的植物时,由于连接线段上存在一棵植物 ( 1 , 2 ) (1, 2) (1,2),会产生 3 3 3 的能量损失。注意,如果一棵植物与能量汇集机器连接的线段上没有植物,则能量损失为 1 1 1。现在要计算总的能量损失。

下面给出了一个能量采集的例子,其中 n = 5 n = 5 n=5 m = 4 m = 4 m=4,一共有 20 20 20 棵植物,在每棵植物上标明了能量汇集机器收集它的能量时产生的能量损失。

在这个例子中,总共产生了 36 36 36 的能量损失。


题意转化:求

( 2 × ∑ i = 1 n ∑ j = 1 m gcd ⁡ ( i , j ) ) − n × m \bigg( 2 \times \sum_{i=1}^{n} \sum_{j=1}^{m} \gcd(i,j) \bigg) - n \times m (2×i=1nj=1mgcd(i,j))n×m

引理:欧拉反演

n = ∑ d ∣ n ϕ ( d ) n = \sum_{d|n} \phi(d) n=dnϕ(d)

现在重点只考虑 ∑ i = 1 n ∑ j = 1 m gcd ⁡ ( i , j ) \sum_{i=1}^{n} \sum_{j=1}^{m} \gcd(i,j) i=1nj=1mgcd(i,j)

转化为: ∑ i = 1 n ∑ j = 1 m ∑ d ∣ g c d ( i , j ) ϕ ( d ) \sum_{i=1}^{n} \sum_{j=1}^{m} \sum_{d|gcd(i,j)} \phi(d) i=1nj=1mdgcd(i,j)ϕ(d)

= ∑ d = 1 n ϕ ( d ) ∑ i = 1 n ∑ j = 1 m [ d ∣ g c d ( i , j ) ] =\sum_{d=1}^{n} \phi(d) \sum_{i=1}^{n} \sum_{j=1}^{m} [d|gcd(i,j)] =d=1nϕ(d)i=1nj=1m[dgcd(i,j)]

= ∑ d = 1 n ϕ ( d ) ⌊ n d ⌋ ⌊ m d ⌋ =\sum_{d=1}^{n} \phi(d) \lfloor \frac{n}{d} \rfloor \lfloor \frac{m}{d} \rfloor =d=1nϕ(d)dndm

直接线性筛 ϕ \phi ϕ 就行了。

#include <bits/stdc++.h>
using namespace std;
const long long N = 1e7 + 10;
long long euler[N], primes[N], sum[N], cnt;
bool st[N];
long long n, m;

void get_eulers(long long n)  // 线性筛法求1~n的欧拉函数
{
    euler[1] = 1;
    for (long long i = 2; i <= n; i ++ ) {
        if (!st[i]) {
            primes[ ++cnt ] = i;
            euler[i] = i - 1;
        }
        for (long long j = 1; primes[j] <= n / i; j ++ ) {
            long long t = primes[j] * i;
            st[t] = true;
            if (i % primes[j] == 0) {
                euler[t] = euler[i] * primes[j];
                break;
            }
            euler[t] = euler[i] * (primes[j] - 1);
        }
    }
}

int main() {
    scanf("%d%d", &n, &m);
    get_eulers(n);
    for (long long i = 1; i <= n; i++) sum[i] = sum[i - 1] + euler[i];
    long long ans = 0;
    for (int d = 1; d <= n; d++) {
        ans += euler[d] * (n / d) * (m / d);
    }
    cout << 2 * ans - n * m;
    return 0;
}

T4 P2522 [HAOI2011]Problem b

题目描述

对于给出的 n n n 个询问,每次求有多少个数对 ( x , y ) (x,y) (x,y),满足 a ≤ x ≤ b a \le x \le b axb c ≤ y ≤ d c \le y \le d cyd,且 gcd ⁡ ( x , y ) = k \gcd(x,y) = k gcd(x,y)=k gcd ⁡ ( x , y ) \gcd(x,y) gcd(x,y) 函数为 x x x y y y 的最大公约数。

简化题意:求

∑ i = a b ∑ j = c d [ gcd ⁡ ( i , j ) = = k ] \sum_{i=a}^{b} \sum_{j=c}^{d} [\gcd(i,j) == k] i=abj=cd[gcd(i,j)==k]

这题有一个类似二维前缀和的思想,或者说是容斥。

A n s = s o l v e ( ( 1 , b ) , ( 1 , d ) ) − s o l v e ( ( 1 , b ) , ( 1 , c − 1 ) ) − s o l v e ( ( 1 , a − 1 ) , ( 1 , d ) ) + s o l v e ( ( 1 , a − 1 ) , ( 1 , c − 1 ) ) Ans = solve((1,b),(1,d))-solve((1,b),(1,c-1))-solve((1,a-1),(1,d))+solve((1,a-1),(1,c-1)) Ans=solve((1,b),(1,d))solve((1,b),(1,c1))solve((1,a1),(1,d))+solve((1,a1),(1,c1))

至此,完成了 1 − n , 1 − m 1-n,1-m 1n,1m的转化。只要求解这个子问题就行了。

P3455 [POI2007]ZAP-Queries

这题即是 T4 的子问题。

题目描述

密码学家正在尝试破解一种叫 BSA 的密码。
他发现,在破解一条消息的同时,他还需要回答这样一种问题:
给出 a , b , d a,b,d a,b,d,求满足 1 ≤ x ≤ a 1 \leq x \leq a 1xa 1 ≤ y ≤ b 1 \leq y \leq b 1yb,且 gcd ⁡ ( x , y ) = d \gcd(x,y)=d gcd(x,y)=d 的二元组 ( x , y ) (x,y) (x,y) 的数量。
因为要解决的问题实在太多了,他便过来寻求你的帮助。

f ( k ) = ∑ i = 1 a ∑ i = 1 b [ gcd ⁡ ( i , j ) = = k ] f(k) = \sum_{i=1}^{a} \sum_{i=1}^{b} [\gcd(i,j)==k] f(k)=i=1ai=1b[gcd(i,j)==k]

g ( k ) = ∑ n ∣ k f ( k ) = ⌊ a n ⌋ ⌊ b n ⌋ g(k) = \sum_{n|k} f(k) = \lfloor \frac{a}{n} \rfloor \lfloor \frac{b}{n} \rfloor g(k)=nkf(k)=nanb

根据莫反有: f ( k ) = ∑ n ∣ k μ ( ⌊ k n ⌋ ) F ( k ) f(k) = \sum_{n|k} \mu(\lfloor \frac{k}{n} \rfloor) F(k) f(k)=nkμ(⌊nk⌋)F(k)

根据题目, A n s = f ( d ) Ans = f(d) Ans=f(d)

A n s Ans Ans 用莫反的式子表示出来:

A n s = ∑ d ∣ k μ ( ⌊ k d ⌋ ) F ( k ) Ans = \sum_{d|k} \mu(\lfloor \frac{k}{d} \rfloor) F(k) Ans=dkμ(⌊dk⌋)F(k)

A n s = ∑ d ∣ k μ ( ⌊ k d ⌋ ) ⌊ a k ⌋ ⌊ b k ⌋ Ans = \sum_{d|k} \mu(\lfloor \frac{k}{d} \rfloor) \lfloor \frac{a}{k} \rfloor \lfloor \frac{b}{k} \rfloor Ans=dkμ(⌊dk⌋)kakb

t = b k t=\frac{b}{k} t=kb,改为枚举 t t t,则:

A n s = ∑ t = 1 min ⁡ ( ⌊ a d ⌋ , ⌊ b d ⌋ ) μ ( t ) ⌊ a t d ⌋ ⌊ b t d ⌋ Ans = \sum_{t=1}^{\min(\lfloor \frac{a}{d} \rfloor , \lfloor \frac{b}{d} \rfloor)} \mu(t) \lfloor \frac{a}{td} \rfloor \lfloor \frac{b}{td} \rfloor Ans=t=1min(⌊da,db⌋)μ(t)tdatdb

至此,该子问题得到了解决,已经是 O ( N ) O(N) O(N) 的效率了。

但是由于多组数据,需要采用数论分块优化到 O ( N ) O(\sqrt N) O(N )

子问题代码:

#include <bits/stdc++.h>
using namespace std;
const int N = 5e5 + 10;
int mu[N], prime[N], tot = 0, sum[N];
bool st[N];

int read(){
    int x = 0; int zf = 1; char ch = ' ';
    while (ch != '-' && (ch < '0' || ch > '9')) ch = getchar();
    if (ch == '-') zf = -1, ch = getchar();
    while (ch >= '0' && ch <= '9') x = x * 10 + ch - '0', ch = getchar(); return x * zf;
}

inline void init(int n) {
	mu[1] = 1;
    for (register int i = 2; i <= n; i++) {
        if (!st[i]) prime[++tot] = i, mu[i] = -1;
        for (register int j = 1; j <= tot && prime[j] * i <= n; j++) {
            st[prime[j] * i] = 1;
            if (i % prime[j] != 0) mu[i * prime[j]] = -mu[i];
            else { mu[prime[j] * i] = 0; break; }
        }
    }
    for (int i = 1; i <= n; i++) sum[i] = sum[i - 1] + mu[i];
}

int k;

int f(int x, int y) {
    int res = 0;
    for (int l = 1, r; l <= min(x, y); l = r + 1) {
        r = min(x / (x / l), y / (y / l));
        res += (sum[r] - sum[l - 1]) * ((x / k) / l) * ((y / k) / l);
    }
    return res;
}

int main() {
	init(50000);
	int T; T = read();
	while (T--) {
		static int a, b; a = read(), b = read(), k = read(); 
		printf("%lld\n", f(a, b));
	}
	return 0;
}

原问题只需要像开头说的一样容斥解决。

代码:

#include <bits/stdc++.h>
using namespace std;
const int N = 5e5 + 10;
int mu[N], prime[N], tot = 0, sum[N];
bool st[N];

int read(){
    int x = 0; int zf = 1; char ch = ' ';
    while (ch != '-' && (ch < '0' || ch > '9')) ch = getchar();
    if (ch == '-') zf = -1, ch = getchar();
    while (ch >= '0' && ch <= '9') x = x * 10 + ch - '0', ch = getchar(); return x * zf;
}

inline void init(int n) {
	mu[1] = 1;
    for (register int i = 2; i <= n; i++) {
        if (!st[i]) prime[++tot] = i, mu[i] = -1;
        for (register int j = 1; j <= tot && prime[j] * i <= n; j++) {
            st[prime[j] * i] = 1;
            if (i % prime[j] != 0) mu[i * prime[j]] = -mu[i];
            else { mu[prime[j] * i] = 0; break; }
        }
    }
    for (int i = 1; i <= n; i++) sum[i] = sum[i - 1] + mu[i];
}

int k;

int f(int x, int y) {
    int res = 0;
    for (int l = 1, r; l <= min(x, y); l = r + 1) {
        r = min(x / (x / l), y / (y / l));
        res += (sum[r] - sum[l - 1]) * ((x / k) / l) * ((y / k) / l);
    }
    return res;
}

int main() {
	init(50000);
	int T; T = read();
	while (T--) {
		static int a, b, c, d; a = read(), b = read(), c = read(), d = read(), k = read(); 
		printf("%lld\n", f(b, d) - f(b, c - 1) - f(a - 1, d) + f(a - 1, c - 1));
	}
	return 0;
}

T5 P4318 完全平方数

题目描述

小 X 自幼就很喜欢数。但奇怪的是,他十分讨厌完全平方数。他觉得这些数看起来很令人难受。由此,他也讨厌所有是完全平方数的正整数倍的数。然而这丝毫不影响他对其他数的热爱。

这天是小X的生日,小 W 想送一个数给他作为生日礼物。当然他不能送一个小X讨厌的数。他列出了所有小X不讨厌的数,然后选取了第 K个数送给了小X。小X很开心地收下了。

然而现在小 W 却记不起送给小X的是哪个数了。你能帮他一下吗?

题意简化:

多组数据,每次求第 K K K不含大于 1 1 1 的完全平方因子的正整数。

由于当时正好学杜教筛,因此就用了杜教筛的方法。

详见 OI-Wiki

杜教筛可以在低于线性时间内处理数论函数的前缀和。
个人认为就是个记忆化+预处理优化。

回到原题,平方因子和莫比乌斯函数有点关系。

题目要求第 K K K 个满足条件的数,因此有: ∑ i = 1 a n s [ μ ( i ) ≠ 0 ] = = K \sum_{i=1}^{ans} [\mu(i) \neq 0] == K i=1ans[μ(i)=0]==K,并且 μ ( a n s ) ≠ 0 \mu(ans) \neq 0 μ(ans)=0

μ \mu μ 函数有三种取值: − 1 , 0 , 1 -1,0,1 1,0,1 − 1 -1 1 不是很好处理,要特判,因此直接用 μ 2 ( n ) \mu^2(n) μ2(n) 来代替 μ ( n ) \mu(n) μ(n)。(并不代表这两个等价,只是在此题中用此方法更方便)

f ( n ) = μ 2 ( n ) , S ( n ) = ∑ i = 1 n f ( i ) f(n) = \mu^2(n), S(n) = \sum_{i=1}^{n} f(i) f(n)=μ2(n),S(n)=i=1nf(i)

因此 S ( a n s ) = = k S(ans) == k S(ans)==k f ( a n s ) ≠ 0 f(ans) \neq 0 f(ans)=0

杜教筛可以在低于线性时间内处理数论函数的前缀和。

数据范围已经高到 1 0 9 10^9 109 了,而且要处理前缀和的 μ 2 \mu^2 μ2 是一个积性函数。

因此考虑杜教筛

这里设 g ( n ) = [ n = k 2 , k ∈ N + ] g(n) = [n = k^2, k \in N_{+}] g(n)=[n=k2,kN+]

再考虑卷积: ( f × g ) ( n ) = ∑ d ∣ n g ( d ) × f ( n d ) (f \times g)(n) = \sum_{d | n} {g(d) \times f( \frac{n}{d} )} (f×g)(n)=dng(d)×f(dn)

这里 g ( d ) × f ( n d ) g(d) \times f( \frac{n}{d} ) g(d)×f(dn) 只有在 d d d n n n 的最大平方因子时为 1 1 1,否则为 0 0 0

它的前缀和是很好求的,因为每个数都有最大平方因子(每个数都是 1 1 1 的倍数)。因此 ( f × g ) ( n ) = 1 ( n ) = 1 (f \times g)(n) = 1(n) = 1 (f×g)(n)=1(n)=1

继续往后推: g ( 1 ) S ( n ) = ∑ i = 1 n ( f × g ) ( i ) − ∑ i = 2 n g ( i ) × S ( ⌊ n i ⌋ ) = n − ∑ i = 2 n g ( i ) × S ( ⌊ n i ⌋ ) g(1)S(n) = \sum_{i=1}^{n} (f \times g)(i) - \sum_{i=2}^{n} g(i) \times S(\lfloor \frac{n}{i} \rfloor) = n - \sum_{i=2}^{n} g(i) \times S(\lfloor \frac{n}{i} \rfloor) g(1)S(n)=i=1n(f×g)(i)i=2ng(i)×S(⌊in⌋)=ni=2ng(i)×S(⌊in⌋)

g g g 函数去掉,即枚举平方因子:

S ( n ) = n − ∑ i = 2 n g ( i ) × S ( ⌊ n i ⌋ ) = n − ∑ i = 2 n S ( ⌊ n i 2 ⌋ ) S(n) = n - \sum_{i=2}^{n} g(i) \times S(\lfloor \frac{n}{i} \rfloor) = n - \sum_{i=2}^{\sqrt n} S(\lfloor \frac{n}{i^2} \rfloor) S(n)=ni=2ng(i)×S(⌊in⌋)=ni=2n S(⌊i2n⌋)

到这里就可以直接杜教筛了。

#include <bits/stdc++.h>
using namespace std;
const int N = 1005010;

int mu[N], prime[N], sum[N], tot = 0;
bool st[N];

inline void init(int n) {
	sum[1] = mu[1] = 1;
    for (register long long i = 2; i <= n; i++) {
        if (!st[i]) prime[++tot] = i, mu[i] = 1;
        for (register int j = 1; j <= tot && prime[j] * i <= n; j++) {
            st[prime[j] * i] = 1;
            if (i % prime[j] != 0) mu[i * prime[j]] = mu[i];
            else { mu[prime[j] * i] = 0; break; }
        }
		sum[i] = sum[i - 1] + mu[i];
    }
}

int T, n;

map<int, int> mp;

int dfs(int x) {
	if (x <= N - 3) return sum[x];
	if (mp[x]) return mp[x];
	int ans = x;
	for (int i = 2; i * i <= x; i++) ans -= dfs(x / (i * i));
	mp[x] = ans;
	return ans;
}

int main() {
	init(N - 3);
	scanf("%d", &T);
	while (T--) {
		scanf("%d", &n);
		long long l = n, r = 2 * n;
		while (l < r) {
			long long mid = l + r >> 1;
			if (dfs(mid) >= n) r = mid;
			else l = mid + 1;
		}
		printf("%lld\n", r);
	}
	return 0;
}

T6 P2257 YY的GCD

题目描述

神犇 YY 虐完数论后给傻× kAc 出了一题

给定 N , M N, M N,M,求 1 ≤ x ≤ N 1 \leq x \leq N 1xN 1 ≤ y ≤ M 1 \leq y \leq M 1yM gcd ⁡ ( x , y ) \gcd(x, y) gcd(x,y) 为质数的 ( x , y ) (x, y) (x,y) 有多少对。


简化题意:求 ∑ i = 1 N ∑ j = 1 M [ gcd ⁡ ( i , j ) ∈ P r i m e ] \sum_{i=1}^{N} \sum_{j=1}^{M} [\gcd(i,j) \in Prime] i=1Nj=1M[gcd(i,j)Prime]

提出 p p p
∑ p ∈ P r i m e ∑ i = 1 N ∑ j = 1 M [ gcd ⁡ ( i , j ) = = p ] \sum_{p \in Prime} \sum_{i=1}^{N} \sum_{j=1}^{M} [\gcd(i,j) == p] pPrimei=1Nj=1M[gcd(i,j)==p]

套路:
∑ p ∈ P r i m e ∑ i = 1 ⌊ N p ⌋ ∑ j = 1 ⌊ M p ⌋ [ gcd ⁡ ( i , j ) = = 1 ] \sum_{p \in Prime} \sum_{i=1}^{\lfloor \frac{N}{p} \rfloor} \sum_{j=1}^{\lfloor \frac{M}{p} \rfloor} [\gcd(i,j) == 1] pPrimei=1pNj=1pM[gcd(i,j)==1]

∑ p ∈ P r i m e ∑ i = 1 ⌊ N p ⌋ ∑ j = 1 ⌊ M p ⌋ [ gcd ⁡ ( i , j ) = = 1 ] \sum_{p \in Prime} \sum_{i=1}^{\lfloor \frac{N}{p} \rfloor} \sum_{j=1}^{\lfloor \frac{M}{p} \rfloor} [\gcd(i,j) == 1] pPrimei=1pNj=1pM[gcd(i,j)==1]

∑ p ∈ P r i m e ∑ i = 1 ⌊ N p ⌋ ∑ j = 1 ⌊ M p ⌋ ∑ d ∣ i , d ∣ j μ ( d ) \sum_{p \in Prime} \sum_{i=1}^{\lfloor \frac{N}{p} \rfloor} \sum_{j=1}^{\lfloor \frac{M}{p} \rfloor} \sum_{d|i,d|j} \mu(d) pPrimei=1pNj=1pMdi,djμ(d)

∑ p ∈ P r i m e ∑ d = 1 min ⁡ ( N , M ) p μ ( d ) × ⌊ N p d ⌋ ⌊ M p d ⌋ \sum_{p \in Prime} \sum_{d=1}^{\frac{\min(N,M)}{p}} \mu(d) \times \lfloor \frac{N}{pd} \rfloor \lfloor \frac{M}{pd} \rfloor pPrimed=1pmin(N,M)μ(d)×pdNpdM

t = p d t=pd t=pd

∑ p ∈ P r i m e ∑ d = 1 min ⁡ ( N , M ) p μ ( d ) × ⌊ N t ⌋ ⌊ M t ⌋ \sum_{p \in Prime} \sum_{d=1}^{\frac{\min(N,M)}{p}} \mu(d) \times \lfloor \frac{N}{t} \rfloor \lfloor \frac{M}{t} \rfloor pPrimed=1pmin(N,M)μ(d)×tNtM

∑ t = 1 min ⁡ ( N , M ) ⌊ N t ⌋ ⌊ M t ⌋ ∑ d ∈ P r i m e , d ∣ t μ ( t d ) \sum_{t=1}^{\min(N,M)} \lfloor \frac{N}{t} \rfloor \lfloor \frac{M}{t} \rfloor \sum_{d \in Prime, d | t} \mu(\frac{t}{d}) t=1min(N,M)tNtMdPrime,dtμ(dt)

套路一下,设 F ( x ) = ∑ d ∈ P r i m e , d ∣ x μ ( x d ) F(x)=\sum_{d \in Prime, d | x} \mu(\frac{x}{d}) F(x)=dPrime,dxμ(dx)

接下来前面部分数论分块就好了。

#include <bits/stdc++.h>
using namespace std;
const int N = 1e7 + 10;
int mu[N], prime[N], tot = 0, f[N];
bool st[N];

void init(int n) {
	mu[1] = 1;
    for (int i = 2; i <= n; i++) {
        if (!st[i]) prime[++tot] = i, mu[i] = -1;
        for (int j = 1; j <= tot && prime[j] * i <= n; j++) {
            st[prime[j] * i] = 1;
            if (i % prime[j] != 0) mu[i * prime[j]] = -mu[i];
            else { mu[prime[j] * i] = 0; break; }
        }
    }
    for (int i = 1; i <= tot; i++)
        for (int j = prime[i]; j <= n; j += prime[i]) f[j] += mu[j / prime[i]];
    for (int i = 1; i <= n; i++) f[i] += f[i - 1];
}

int main() {
    init(1e7);
    int T, n, m, nn;
    scanf("%d", &T);
    while (T--) {
        scanf("%d%d", &n, &m); nn = min(n, m);
        long long ans = 0, l = 0, r = 0;
        for (int i = 1; i <= nn;) {
            l = i; long long x = n / l, y = m / l;
            r = min(n / x, m / y);
            ans += 1ll * x * 1ll * y * 1ll * (f[r] - f[l - 1]);
            i = r + 1;
        }
        cout << ans << endl;
    }
    return 0;
}

T7 P1829 【国家集训队】Crash的数字表格 / JZPTAB

题目描述

今天的数学课上,Crash 小朋友学习了最小公倍数(Least Common Multiple)。对于两个正整数 a a a b b b lcm ( a , b ) \text{lcm}(a,b) lcm(a,b) 表示能同时整除 a a a b b b 的最小正整数。例如, lcm ( 6 , 8 ) = 24 \text{lcm}(6, 8) = 24 lcm(6,8)=24

回到家后,Crash 还在想着课上学的东西,为了研究最小公倍数,他画了一张 $ n \times m$ 的表格。每个格子里写了一个数字,其中第 i i i 行第 j j j 列的那个格子里写着数为 lcm ( i , j ) \text{lcm}(i, j) lcm(i,j)

看着这个表格,Crash 想到了很多可以思考的问题。不过他最想解决的问题却是一个十分简单的问题:这个表格中所有数的和是多少。当 n n n m m m 很大时,Crash 就束手无策了,因此他找到了聪明的你用程序帮他解决这个问题。由于最终结果可能会很大,Crash 只想知道表格里所有数的和   m o d   20101009 \bmod 20101009 mod20101009 的值。


题目简化:求 ∑ i = 1 n ∑ j = 1 m l c m ( i , j ) \sum_{i=1}^{n} \sum_{j=1}^{m} lcm(i,j) i=1nj=1mlcm(i,j)

∑ i = 1 n ∑ j = 1 m i × j gcd ⁡ ( i , j ) \sum_{i=1}^{n} \sum_{j=1}^{m} \frac{i \times j}{\gcd(i,j)} i=1nj=1mgcd(i,j)i×j

∑ d = 1 min ⁡ ( n , m ) ∑ i = 1 n ∑ j = 1 m i × j d [ gcd ⁡ ( i , j ) = = d ] \sum_{d=1}^{\min(n,m)} \sum_{i=1}^{n} \sum_{j=1}^{m} \frac{i \times j}{d} [\gcd(i,j) == d] d=1min(n,m)i=1nj=1mdi×j[gcd(i,j)==d]

∑ d = 1 min ⁡ ( n , m ) d ∑ i = 1 ⌊ n d ⌋ ∑ j = 1 ⌊ m d ⌋ i × j × [ gcd ⁡ ( i , j ) = = 1 ] \sum_{d=1}^{\min(n,m)} d \sum_{i=1}^{\lfloor \frac{n}{d} \rfloor} \sum_{j=1}^{\lfloor \frac{m}{d} \rfloor} i \times j \times [\gcd(i,j) == 1] d=1min(n,m)di=1dnj=1dmi×j×[gcd(i,j)==1]

考虑后面部分:

s ( n , m ) = ∑ i = 1 n ∑ j = 1 m i × j × [ gcd ⁡ ( i , j ) = = 1 ] s(n,m)=\sum_{i=1}^{n} \sum_{j=1}^{m} i \times j \times [\gcd(i,j) == 1] s(n,m)=i=1nj=1mi×j×[gcd(i,j)==1]

s ( n , m ) = ∑ d = 1 min ⁡ ( n , m ) μ ( d ) × ∑ d ∣ i n ∑ d ∣ j m i × j s(n,m)=\sum_{d=1}^{\min(n,m)} \mu(d) \times \sum_{d|i}^{n} \sum_{d|j}^{m} i \times j s(n,m)=d=1min(n,m)μ(d)×dindjmi×j

s ( n , m ) = ∑ d = 1 min ⁡ ( n , m ) μ ( d ) × d 2 × ∑ i = 1 ⌊ n d ⌋ ∑ j = 1 ⌊ m d ⌋ i × j s(n,m)=\sum_{d=1}^{\min(n,m)} \mu(d) \times d^2 \times \sum_{i=1}^{\lfloor \frac{n}{d} \rfloor} \sum_{j=1}^{\lfloor \frac{m}{d} \rfloor} i \times j s(n,m)=d=1min(n,m)μ(d)×d2×i=1dnj=1dmi×j

这个柿子前半部分可以直接前缀和,后半部分可以公式计算:

s u m ( n , m ) = ∑ i = 1 n ∑ j = 1 m i × j = n × ( n + 1 ) 2 × m × ( m + 1 ) 2 sum(n,m)=\sum_{i=1}^{n} \sum_{j=1}^{m} i \times j = \frac{n \times (n + 1)}{2} \times \frac{m \times (m + 1)}{2} sum(n,m)=i=1nj=1mi×j=2n×(n+1)×2m×(m+1)

则代入原来的式子:

s ( n , m ) = ∑ d = 1 min ⁡ ( n , m ) μ ( d ) × d 2 × s u m ( ⌊ n d ⌋ , ⌊ m d ⌋ ) s(n,m)=\sum_{d=1}^{\min(n,m)} \mu(d) \times d^2 \times sum(\lfloor \frac{n}{d} \rfloor , \lfloor \frac{m}{d} \rfloor) s(n,m)=d=1min(n,m)μ(d)×d2×sum(⌊dn,dm⌋)

至此, s s s 函数可以用数论分块解决了。

带回原式:

A n s = ∑ d = 1 n d × s ( ⌊ n d ⌋ , ⌊ m d ⌋ ) Ans = \sum_{d=1}^{n} d \times s(\lfloor \frac{n}{d} \rfloor, \lfloor \frac{m}{d} \rfloor) Ans=d=1nd×s(⌊dn,dm⌋)

这个式子也可以数论分块

因此这道题双重数论分块即可解决。

#include <bits/stdc++.h>
using namespace std;

const long long N = 1e7 + 10, mod = 20101009;

long long mu[N], prime[N], sum[N], tot;
bool st[N];

inline void init(long long n) {
	mu[1] = 1;
    for (register long long i = 2; i <= n; i++) {
        if (!st[i]) prime[++tot] = i, mu[i] = -1;
        for (register long long j = 1; j <= tot && prime[j] * i <= n; j++) {
            st[prime[j] * i] = 1;
            if (i % prime[j] != 0) mu[i * prime[j]] = -mu[i];
            else { mu[prime[j] * i] = 0; break; }
        }
    }
    for (long long i = 1; i <= n; i++) sum[i] = (sum[i - 1] + mu[i] * 1ll * i * i % mod + mod) % mod;
}

long long n, m;

long long Sum(long long x, long long y) {
	return (x * (x + 1) / 2 % mod) * (y * (y + 1) / 2 % mod) % mod;
}

inline long long query(long long x, long long y) {
	long long ans = 0;
	for (long long l = 1, r; l <= min(x, y); l = r + 1) {
		r = min(x / (x / l), y / (y / l));
		ans = ans + ((sum[r] - sum[l - 1] + mod) * 1ll * Sum(x / l, y / l) % mod) % mod;
	}
	return ans;
}

int main() {
	scanf("%lld%lld", &n, &m);
	init(1e7);
	long long ans = 0;
	for (long long l = 1, r; l <= min(n, m); l = r + 1) {
		r = min(n / (n / l), m / (m / l));
		ans = (ans + ((r - l + 1) * (l + r) / 2 % mod * (query(n / l, m / l)) % mod)) % mod;
	}
	printf("%lld\n", ans);
	return 0;
}

T8 P3327 【SDOI2015】约数个数和

题目描述

d ( x ) d(x) d(x) x x x 的约数个数,给定 n , m n,m n,m,求
∑ i = 1 n ∑ j = 1 m d ( i j ) \sum_{i=1}^n\sum_{j=1}^md(ij) i=1nj=1md(ij)


已知一个公式: d ( i j ) = ∑ x ∣ i ∑ y ∣ j [ gcd ⁡ ( x , y ) = = 1 ] d(ij) = \sum_{x|i} \sum_{y|j} [\gcd(x,y) == 1] d(ij)=xiyj[gcd(x,y)==1]
则: A n s = ∑ i = 1 n ∑ j = 1 m ∑ x ∣ i ∑ y ∣ j [ gcd ⁡ ( x , y ) = = 1 ] Ans = \sum_{i=1}^{n} \sum_{j=1}^{m} \sum_{x|i} \sum_{y|j} [\gcd(x,y) == 1] Ans=i=1nj=1mxiyj[gcd(x,y)==1]

转化式子: ∑ i = 1 n ∑ j = 1 m ⌊ n i ⌋ ⌊ m j ⌋ [ gcd ⁡ ( x , y ) = = 1 ] \sum_{i=1}^{n} \sum_{j=1}^{m} \lfloor \frac{n}{i} \rfloor \lfloor \frac{m}{j} \rfloor [\gcd(x,y) == 1] i=1nj=1minjm[gcd(x,y)==1]

开始莫反: f ( x ) = ∑ i = 1 n ∑ j = 1 m ⌊ n i ⌋ ⌊ m j ⌋ [ gcd ⁡ ( x , y ) = = x ] f(x)=\sum_{i=1}^{n} \sum_{j=1}^{m} \lfloor \frac{n}{i} \rfloor \lfloor \frac{m}{j} \rfloor [\gcd(x,y) == x] f(x)=i=1nj=1minjm[gcd(x,y)==x]

g ( x ) = ∑ x ∣ d f ( d ) g(x) = \sum_{x|d} f(d) g(x)=xdf(d)

g ( x ) = ∑ i = 1 n x ∑ j = 1 m x ⌊ n i x ⌋ ⌊ m j x ⌋ g(x) = \sum_{i=1}^{\frac{n}{x}} \sum_{j=1}^{\frac{m}{x}} \lfloor \frac{n}{ix} \rfloor \lfloor \frac{m}{jx} \rfloor g(x)=i=1xnj=1xmixnjxm

同时表示出 f f f 函数:
f ( n ) = ∑ n ∣ d μ ( d n ) g ( d ) f(n) = \sum_{n|d} \mu(\frac{d}{n}) g(d) f(n)=ndμ(nd)g(d)

答案明显是 f ( 1 ) f(1) f(1)
A n s = f ( 1 ) = ∑ i = 1 n μ ( i ) g ( i ) Ans = f(1) = \sum_{i=1}^{n} \mu(i) g(i) Ans=f(1)=i=1nμ(i)g(i)

最后是计算 g ( i ) g(i) g(i) 的问题:先计算 s ( n ) = ∑ i = 1 n ⌊ n i ⌋ s(n) = \sum_{i=1}^{n} \lfloor \frac{n}{i} \rfloor s(n)=i=1nin

这样 A n s Ans Ans 即可分为前后两部分( μ ( i ) \mu(i) μ(i) g ( i ) g(i) g(i))计算。

前面 μ \mu μ 直接线性筛并前缀和,后面 g g g 函数用 s s s 直接求解。

#include <bits/stdc++.h>
using namespace std;
const long long N = 5e4 + 10;

long long s[N], mu[N], tot = 0, prime[N], sum[N];
bool st[N];

inline void init(long long n) {
	sum[1] = mu[1] = 1;
    for (register long long i = 2; i <= n; i++) {
        if (!st[i]) prime[++tot] = i, mu[i] = -1;
        for (register long long j = 1; j <= tot && prime[j] * i <= n; j++) {
            st[prime[j] * i] = 1;
            if (i % prime[j] != 0) mu[i * prime[j]] = -mu[i];
            else { mu[prime[j] * i] = 0; break; }
        }
		sum[i] = sum[i - 1] + mu[i];
    }
    for (long long i = 1; i <= n; i++) {
        for (long long l = 1, r; l <= i; l = r + 1) {
            r = i / (i / l);
            s[i] += (r - l + 1) * (i / l);
        }
    }
}

long long n, m;

int main() {
    long long T; scanf("%lld", &T);
    init(5e4);
    while (T--) {
        scanf("%lld%lld", &n, &m);
        if (n > m) swap(n, m);
        long long ans = 0;
        for (long long l = 1, r; l <= n; l = r + 1) {
            r = min(n / (n / l), m / (m / l));
            ans += (sum[r] - sum[l - 1]) * s[n / l] * s[m / l];
        }
        printf("%lld\n", ans);
    }
    return 0;
}

T9 P4449 于神之怒加强版

题目描述

给定 n , m , k n,m,k n,m,k,计算

∑ i = 1 n ∑ j = 1 m gcd ⁡ ( i , j ) k \sum_{i=1}^n \sum_{j=1}^m \gcd(i,j)^k i=1nj=1mgcd(i,j)k

1 0 9 + 7 10^9 + 7 109+7 取模的结果。


∑ i = 1 n ∑ j = 1 m gcd ⁡ ( i , j ) k \sum_{i=1}^n \sum_{j=1}^m \gcd(i,j)^k i=1nj=1mgcd(i,j)k

枚举 gcd ⁡ \gcd gcd,得:

∑ i = 1 n ∑ j = 1 m ∑ d = 1 n d k [ gcd ⁡ ( i , j ) = = 1 ] \sum_{i=1}^{n} \sum_{j=1}^{m} \sum_{d=1}^{n} d^{k} [\gcd(i,j) == 1] i=1nj=1md=1ndk[gcd(i,j)==1]

∑ d = 1 d k ∑ i = 1 ⌊ n d ⌋ ∑ j = 1 ⌊ m d ⌋ ∑ x ∣ i , x ∣ j μ ( x ) \sum_{d=1} d^{k} \sum_{i=1}^{\lfloor \frac{n}{d} \rfloor} \sum_{j=1}^{\lfloor \frac{m}{d} \rfloor} \sum_{x|i,x|j} \mu(x) d=1dki=1dnj=1dmxi,xjμ(x)

∑ d = 1 n d k ∑ x = 1 μ ( x ) ⌊ n d x ⌋ ⌊ m d x ⌋ \sum_{d=1}^{n} d^{k} \sum_{x=1} \mu(x) \lfloor \frac{n}{dx} \rfloor \lfloor \frac{m}{dx} \rfloor d=1ndkx=1μ(x)dxndxm

∑ d = 1 n d k ∑ x = 1 μ ( x ) ⌊ n d x ⌋ ⌊ m d x ⌋ \sum_{d=1}^{n} d^k \sum_{x=1} \mu(x) \lfloor \frac{n}{dx} \rfloor \lfloor \frac{m}{dx} \rfloor d=1ndkx=1μ(x)dxndxm

简化式子,设 T = d x T = dx T=dx

∑ T = 1 n ⌊ n T ⌋ ⌊ m T ⌋ ∑ d ∣ T d k μ ( T d ) \sum_{T=1}^{n} \lfloor \frac{n}{T} \rfloor \lfloor \frac{m}{T} \rfloor \sum_{d|T} d^{k} \mu(\frac{T}{d}) T=1nTnTmdTdkμ(dT)

插入证明积性函数的卷积仍是积性函数
f ( x ) f(x) f(x) g ( x ) g(x) g(x) 都是积性函数。
a a a b b b,其中 gcd ⁡ ( a , b ) = = 1 \gcd(a,b) == 1 gcd(a,b)==1
h ( a ) = ∑ x ∣ a f ( a ) g ( a x ) h(a) = \sum_{x|a} f(a) g(\frac{a}{x}) h(a)=xaf(a)g(xa)
h ( b ) = ∑ y ∣ b f ( b ) g ( b y ) h(b) = \sum_{y|b} f(b) g(\frac{b}{y}) h(b)=ybf(b)g(yb)
接下来证明 h ( a ) h ( b ) = = h ( a b ) h(a)h(b) == h(ab) h(a)h(b)==h(ab)
h ( a ) h ( b ) = ∑ x ∣ a f ( a ) g ( a x ) ∑ y ∣ b f ( b ) g ( b y ) h(a)h(b) = \sum_{x|a} f(a) g(\frac{a}{x}) \sum_{y|b} f(b) g(\frac{b}{y}) h(a)h(b)=xaf(a)g(xa)ybf(b)g(yb)
= ∑ d ∣ a b f ( d ) g ( a b d ) = \sum_{d|ab} f(d) g(\frac{ab}{d}) =dabf(d)g(dab)
= h ( a b ) = h(ab) =h(ab)

回到原式: ∑ T = 1 n ⌊ n T ⌋ ⌊ m T ⌋ ∑ d ∣ T d k μ ( T d ) \sum_{T=1}^{n} \lfloor \frac{n}{T} \rfloor \lfloor \frac{m}{T} \rfloor \sum_{d|T} d^{k} \mu(\frac{T}{d}) T=1nTnTmdTdkμ(dT)

只观察后半部分: ∑ d ∣ T d k μ ( T d ) \sum_{d|T} d^{k} \mu(\frac{T}{d}) dTdkμ(dT)

很明显 d k d^k dk μ \mu μ 都是积性函数,而且这是一个卷积的形式。

因此利用上面插入证明的结论:积性函数的卷积仍是积性函数

也就是说 g ( T ) = ∑ d ∣ T d k μ ( T d ) g(T) = \sum_{d|T} d^{k} \mu(\frac{T}{d}) g(T)=dTdkμ(dT) 也是积性函数

众所周知积性函数可以用线性筛。因此 g g g 函数也可以用筛法。

下面分类讨论两种情况:

在这里插入图片描述

线性筛 g g g 函数即可。

#include <bits/stdc++.h>
using namespace std;
const int N = 5e6 + 10;
const int mod = 1e9 + 7;
bool st[N];
int prime[N], tot = 0, g[N], ksm[N];

int qmi(int a, int b) {
	int res = 1 % mod;
	while (b) {
		if (b & 1) res = (long long)res * a % mod;
		a = (long long)a * a % mod;
		b >>= 1;
	}
	return res;
}

int k;

void init(int n) {
	g[1] = 1;
	for (int i = 2; i <= n; i++) {
		if (!st[i]) prime[++tot] = i, ksm[tot] = qmi(i, k), g[i] = (ksm[tot] - 1) % mod;
		for (int j = 1; j <= tot && i * prime[j] <= n; j++) {
			int p = prime[j] * i;
			st[p] = 1;
			if (i % prime[j] == 0) {
				g[p] = 1ll * ksm[j] * g[i] % mod;
				break;
			}
			g[p] = 1ll * g[i] * g[prime[j]] % mod;
		}
	}
	for (int i = 2; i <= n; i++) g[i] += g[i - 1], g[i] %= mod;
}

int T, n, m;

int main() {
	scanf("%d%d", &T, &k);
	init(N - 5);
	while (T--) {
		scanf("%d%d", &n, &m);
		int ans = 0;
		for (int l = 1, r; l <= min(n, m); l = r + 1) {
			r = min(n / (n / l), m / (m / l));
			ans += ((g[r] - g[l - 1] + mod) % mod * 1ll * (n / l) % mod * (m / l) % mod) % mod;
			ans %= mod;
		}
		printf("%d\n", ans);
	}
	return 0;
}

T10 P3312 【SDOI2014】数表

题目描述

有一张 n × m n\times m n×m 的数表,其第 i i i 行第 j j j 列( 1 ≤ i ≤ n 1\le i\le n 1in 1 ≤ j ≤ m 1\le j\le m 1jm)的数值为能同时整除 i i i j j j 的所有自然数之和。给定 a a a,计算数表中不大于 a a a 的数之和。


简化题意:求 ∑ i = 1 n ∑ j = 1 m σ ( gcd ⁡ ( i , j ) ) [ σ ( gcd ⁡ ( i , j ) ) ≤ a ] \sum_{i=1}^{n} \sum_{j=1}^{m} \sigma(\gcd(i,j)) [\sigma(\gcd(i,j)) \leq a] i=1nj=1mσ(gcd(i,j))[σ(gcd(i,j))a]

其中 σ ( x ) \sigma(x) σ(x) 表示 x x x 的约数和。

目前考虑没有 a a a 的限制的情况。

a n s = ∑ d = 1 min ⁡ ( n , m ) σ ( d ) ∑ i = 1 ⌊ n d ⌋ ∑ j = 1 ⌊ m d ⌋ [ gcd ⁡ ( i , j ) = = 1 ] ans = \sum_{d=1}^{\min(n,m)} \sigma(d) \sum_{i=1}^{\lfloor \frac{n}{d} \rfloor} \sum_{j=1}^{\lfloor \frac{m}{d} \rfloor} [\gcd(i,j)==1] ans=d=1min(n,m)σ(d)i=1dnj=1dm[gcd(i,j)==1]

∑ d = 1 min ⁡ ( n , m ) σ ( d ) ∑ i = 1 ⌊ n d ⌋ ∑ j = 1 ⌊ m d ⌋ ∑ k ∣ i , k ∣ j μ ( k ) \sum_{d=1}^{\min(n,m)} \sigma(d) \sum_{i=1}^{\lfloor \frac{n}{d} \rfloor} \sum_{j=1}^{\lfloor \frac{m}{d} \rfloor} \sum_{k|i,k|j} \mu(k) d=1min(n,m)σ(d)i=1dnj=1dmki,kjμ(k)

∑ d = 1 min ⁡ ( n , m ) σ ( d ) ∑ k = 1 ⌊ min ⁡ ( n , m ) d ⌋ μ ( k ) ⌊ n d k ⌋ ⌊ m d k ⌋ \sum_{d=1}^{\min(n,m)} \sigma(d) \sum_{k=1}^{\lfloor \frac{\min(n,m)}{d} \rfloor} \mu(k) \lfloor \frac{n}{dk} \rfloor \lfloor \frac{m}{dk} \rfloor d=1min(n,m)σ(d)k=1dmin(n,m)μ(k)dkndkm

T = d k T=dk T=dk

∑ T = 1 min ⁡ ( n , m ) ∑ d ∣ T σ ( d ) μ ( T d ) ⌊ n T ⌋ ⌊ m T ⌋ \sum_{T=1}^{\min(n,m)} \sum_{d|T} \sigma(d) \mu(\frac{T}{d}) \lfloor \frac{n}{T} \rfloor \lfloor \frac{m}{T} \rfloor T=1min(n,m)dTσ(d)μ(dT)TnTm

加入 a a a 的限制,考虑用树状数组维护 ∑ d σ ( d ) μ ( T d ) \sum_d \sigma(d) \mu(\frac{T}{d}) dσ(d)μ(dT) 的前缀和。

将线性筛的 σ \sigma σ 升序排序,从小到大查询 a a a 并且更新。

#include <bits/stdc++.h>
using namespace std;
const long long N = 1e5 + 10, M = 2e4 + 10;
const long long mod = (1 << 31);

long long n, prime[N], tot, tree[N], g[N], ans[M], mu[N];
pair<int, int> d[N];
bool st[N];

inline void insert(long long x, long long k) {
	for (; x <= N; x += x & -x) tree[x] += k;
}
inline long long query(long long x) {
	long long ans = 0;
	for (; x; x -= x & -x) ans += tree[x];
	return ans;
}

struct ask {
	long long n, m, a, id;
	long long sol() {
		if (n > m) swap(n, m);
		long long res = 0;
		for (long long l = 1, r; l <= n; l = r + 1) {
			r = min(n / (n / l), m / (m / l));
			res += ((query(r) - query(l - 1)) * (n / l) * (m / l));
		}
		return res;
	}
} q[M];

bool cmp(ask a, ask b) {
	return a.a < b.a;
}

void init() {
	mu[1] = 1; d[1] = make_pair(1, 1);
	for (long long i = 2; i <= N; i++) {
		if (!st[i]) prime[++tot] = i, g[i] = i + 1, mu[i] = -1, d[i] = make_pair(i + 1, i);
		for (long long j = 1; j <= tot && i * prime[j] <= N; j++) {
			long long x = i * prime[j];
			st[x] = 1;
			if (i % prime[j] == 0) {
				mu[x] = 0;
				g[x] = g[i] * prime[j] + 1;
				d[x] = make_pair(d[i].first / g[i] * g[x], x);
				break;
			}
			mu[x] = -mu[i];
			g[x] = prime[j] + 1;
			d[x] = make_pair(d[i].first * d[prime[j]].first, x);
		}
	}
}

int main() {
	init();
	long long T; scanf("%lld", &T);
	sort(d + 1, d + 1 + N);
	for (long long i = 1; i <= T; i++)
		scanf("%lld%lld%lld", &q[i].n, &q[i].m, &q[i].a), q[i].id = i;
	sort(q + 1, q + 1 + T, cmp);
	for (long long i = 1, j = 1; i <= T; i++) {
		while (d[j].first <= q[i].a && j <= N) {
			for (long long k = d[j].second; k <= N; k += d[j].second) insert(k, d[j].first * mu[k / d[j].second]);
			j++;
		}
		ans[q[i].id] = q[i].sol();
	}
	for (long long i = 1; i <= T; i++) printf("%lld\n", ans[i] % mod);
	return 0;
}

T11 P3704 【SDOI2017】数字表格

题目背景

Doris 刚刚学习了 fibonacci 数列。用 f i f_i fi 表示数列的第 i i i 项,那么

f 0 = 0 , f 1 = 1 f_0=0,f_1=1 f0=0,f1=1

f n = f n − 1 + f n − 2 , n ≥ 2 f_n=f_{n-1}+f_{n-2},n\geq 2 fn=fn1+fn2,n2

题目描述

Doris 用老师的超级计算机生成了一个 n × m n\times m n×m 的表格,

i i i 行第 j j j 列的格子中的数是 f gcd ⁡ ( i , j ) f_{\gcd(i,j)} fgcd(i,j),其中 gcd ⁡ ( i , j ) \gcd(i,j) gcd(i,j) 表示 i , j i,j i,j 的最大公约数。

Doris 的表格中共有 n × m n\times m n×m 个数,她想知道这些数的乘积是多少。

答案对 1 0 9 + 7 10^9+7 109+7 取模。


简化题意:求 ∏ d = 1 n ∏ i = 1 n ∏ j = 1 m f i b ( gcd ⁡ ( i , j ) ) [ gcd ⁡ ( i , j ) = = d ] \prod_{d=1}^{n} \prod_{i=1}^{n} \prod_{j=1}^{m} fib(\gcd(i,j)) [\gcd(i,j) == d] d=1ni=1nj=1mfib(gcd(i,j))[gcd(i,j)==d]

其中 f i b ( x ) fib(x) fib(x)斐波那契数列 x x x 项。

化简式子,即提出 d d d

∏ d = 1 n f i b ( d ) ∑ i = 1 ⌊ n d ⌋ ∑ j = 1 ⌊ m d ⌋ [ gcd ⁡ ( i , j ) = = 1 ] \prod_{d=1}^{n} fib(d)^{\sum_{i=1}^{\lfloor \frac{n}{d} \rfloor} \sum_{j=1}^{\lfloor \frac{m}{d} \rfloor} [\gcd(i,j) == 1]} d=1nfib(d)i=1dnj=1dm[gcd(i,j)==1]

∵ ∑ i = 1 a ∑ j = 1 b [ gcd ⁡ ( a , b ) = = 1 ] \because \sum_{i=1}^{a} \sum_{j=1}^{b} [\gcd(a,b) == 1] i=1aj=1b[gcd(a,b)==1] ∑ d = 1 a ⌊ a d ⌋ ⌊ b d ⌋ μ ( d ) \sum_{d=1}^{a} \lfloor \frac{a}{d} \rfloor \lfloor \frac{b}{d} \rfloor \mu(d) d=1adadbμ(d) 相等,

∴ \therefore 原式 = ∏ d = 1 n f i b ( d ) ∑ k ⌊ n d k ⌋ ⌊ m d k ⌋ μ ( k ) = \prod_{d=1}^{n} fib(d) ^{\sum_{k} \lfloor \frac{n}{dk} \rfloor \lfloor \frac{m}{dk} \rfloor \mu(k)} =d=1nfib(d)kdkndkmμ(k)

T = d k T=dk T=dk

∏ T = 1 n ∏ k ∣ T f i b ( T k ) μ ( k ) ⌊ n T ⌋ ⌊ m T ⌋ \prod_{T=1}^{n} \prod_{k|T} fib(\frac{T}{k}) ^{\mu(k) \lfloor \frac{n}{T} \rfloor \lfloor \frac{m}{T} \rfloor} T=1nkTfib(kT)μ(k)TnTm

= ∏ T = 1 n ∏ k ∣ T ( f i b ( T k ) μ ( k ) ) ⌊ n T ⌋ ⌊ m T ⌋ =\prod_{T=1}^{n} \prod_{k|T} (fib(\frac{T}{k}) ^{\mu(k)})^{\lfloor \frac{n}{T} \rfloor \lfloor \frac{m}{T} \rfloor} =T=1nkT(fib(kT)μ(k))TnTm

对于中间的 ( f i b ( T k ) μ ( k ) ) (fib(\frac{T}{k}) ^{\mu(k)}) (fib(kT)μ(k)),可以直接 n   log ⁡   n n~\log~n n log n 预处理前缀和。

对于旁边的指数 ⌊ n T ⌋ ⌊ m T ⌋ ^{\lfloor \frac{n}{T} \rfloor \lfloor \frac{m}{T} \rfloor} TnTm 可以直接数论分块。

注意当需要以 μ \mu μ 为指数的时候,因为 μ \mu μ 可能是 − 1 -1 1,所以需要预先求出 f i b fib fib 数组的逆元。

f i b fib fib 数组直接暴力即可。

#include <bits/stdc++.h>
using namespace std;
const int N = 1e6 + 10, mod = 1e9 + 7;
int mu[N], prime[N], tot = 0, sum[N], fib[N], inv_fib[N];
bool st[N];

int read(){
    int x = 0; int zf = 1; char ch = ' ';
    while (ch != '-' && (ch < '0' || ch > '9')) ch = getchar();
    if (ch == '-') zf = -1, ch = getchar();
    while (ch >= '0' && ch <= '9') x = x * 10 + ch - '0', ch = getchar(); return x * zf;
}

int qmi(int a, int b, int p) {
    int res = 1;
    while (b) {
        if (b & 1) res = res * 1ll * a % p;
        b >>= 1;
        a = a * 1ll * a % p;
    }
    return res;
}

int ny(int a) { return qmi(a, mod - 2, mod); }

inline void init(int n) {
	fib[0] = 0, fib[1] = 1;
	for (int i = 2; i <= n; i++) fib[i] = (fib[i - 1] + fib[i - 2]) % mod, inv_fib[i] = ny(fib[i]);
    mu[1] = 1; inv_fib[1] = 1;
    for (register int i = 2; i <= n; i++) {
        if (!st[i]) prime[++tot] = i, mu[i] = -1;
        for (register int j = 1; j <= tot && prime[j] * i <= n; j++) {
            st[prime[j] * i] = 1;
            if (i % prime[j] != 0) mu[i * prime[j]] = -mu[i];
            else { mu[prime[j] * i] = 0; break; }
        }
    }
    for (int i = 0; i <= n; i++) sum[i] = 1;
    for (int i = 1; i <= n; i++) {
    	if (mu[i] == 0) continue;
    	for (int j = i; j <= n; j += i) {
    		sum[j] = (long long) sum[j] * (mu[i] == 1 ? fib[j / i] : inv_fib[j / i]) % mod;
		}
	}
	for (int i = 1; i <= n; i++) sum[i] = (long long) sum[i - 1] * sum[i] % mod;
}

int solve(int n, int m) {
	if (n > m) swap(n, m);
	int ans = 1;
    for (int l = 1, r; l <= n; l = r + 1) {
        r = min(n / (n / l), m / (m / l));
        ans = (long long) ans * qmi(1ll * sum[r] * ny(sum[l - 1]) % mod, 1ll * (n / l) * (m / l) % (mod - 1), mod) % mod;
    }
    return ans;
}

int n, m;

int main() {
	init(N - 10);
	int T; scanf("%d", &T);
	while (T--) {
		scanf("%d%d", &n, &m);
		printf("%d\n", solve(n, m));
	}
	return 0;
}

Markdown代码超过 1000 行了

T12 CF1780F Three Chairs

题面翻译

给定一个数组 a 1 , a 2 , ⋯   , a n a_1,a_2,\cdots,a_n a1,a2,,an,求满足以下条件的三元组 ( a k 1 , a k 2 , a k 3 ) (a_{k_1},a_{k_2},a_{k_3}) (ak1,ak2,ak3)个数:
gcd ⁡ ( max ⁡ ( a k 1 , a k 2 , a k 3 ) , min ⁡ ( a k 1 , a k 2 , a k 3 ) ) = = 1 \gcd(\max(a_{k_1},a_{k_2},a_{k_3}), \min(a_{k_1},a_{k_2},a_{k_3}))==1 gcd(max(ak1,ak2,ak3),min(ak1,ak2,ak3))==1


首先对于 gcd ⁡ ( max ⁡ ( a k 1 , a k 2 , a k 3 ) , min ⁡ ( a k 1 , a k 2 , a k 3 ) ) = = 1 \gcd(\max(a_{k_1},a_{k_2},a_{k_3}), \min(a_{k_1},a_{k_2},a_{k_3}))==1 gcd(max(ak1,ak2,ak3),min(ak1,ak2,ak3))==1,假设 a k 1 ≤ a k 2 ≤ a k 3 a_{k_1} \le a_{k_2} \le a_{k_3} ak1ak2ak3,则原式变为: gcd ⁡ ( a k 1 , a k 3 ) = = 1 \gcd(a_{k_1},a_{k_3}) == 1 gcd(ak1,ak3)==1

发现它与 a k 2 a_{k_2} ak2 没有任何关系。因此只需要枚举最大值和最小值

那么根据原来的假设,我们先把 a a a 数组排序,接下来的工作就减轻很多。

答案即对于每个 d ∣ a i d|a_i dai,把 i i i 加入序列 q q q。选择最大最小值以后再挑选中间的数。因此就是挑选 q i > q j q_i > q_j qi>qj 的所有数对的 q i − q j − 1 q_i - q_j - 1 qiqj1 之和。

开始莫反:

f ( d ) f(d) f(d) 表示满足以下条件的三元组 ( a k 1 , a k 2 , a k 3 ) (a_{k_1},a_{k_2},a_{k_3}) (ak1,ak2,ak3) 的个数 gcd ⁡ ( max ⁡ ( a k 1 , a k 2 , a k 3 ) , min ⁡ ( a k 1 , a k 2 , a k 3 ) ) = = d \gcd(\max(a_{k_1},a_{k_2},a_{k_3}), \min(a_{k_1},a_{k_2},a_{k_3}))==d gcd(max(ak1,ak2,ak3),min(ak1,ak2,ak3))==d

g ( d ) g(d) g(d) 表示满足以下条件的三元组 ( a k 1 , a k 2 , a k 3 ) (a_{k_1},a_{k_2},a_{k_3}) (ak1,ak2,ak3) 的个数 d ∣ gcd ⁡ ( max ⁡ ( a k 1 , a k 2 , a k 3 ) , min ⁡ ( a k 1 , a k 2 , a k 3 ) ) d| \gcd(\max(a_{k_1},a_{k_2},a_{k_3}), \min(a_{k_1},a_{k_2},a_{k_3})) dgcd(max(ak1,ak2,ak3),min(ak1,ak2,ak3))

g ( x ) = ∑ x ∣ d f ( d ) g(x) = \sum_{x|d} f(d) g(x)=xdf(d)

f ( x ) = ∑ x ∣ d μ ( d x ) g ( d ) f(x) = \sum_{x|d} \mu(\frac{d}{x}) g(d) f(x)=xdμ(xd)g(d)

答案显然为 f ( 1 ) f(1) f(1),即 ∑ i = 1 n μ ( i ) g ( i ) \sum_{i=1}^{n} \mu(i) g(i) i=1nμ(i)g(i)

接下来直接按照上述方式解即可。

#include <bits/stdc++.h>
using namespace std;
const long long N = 3e5 + 10;
long long mu[N], prime[N] , tot = 0;
bool st[N];
long long n, a[N];

inline void init(long long n) {
    mu[1] = 1;
    for (register long long i = 2; i <= n; i++) {
        if (!st[i]) prime[++tot] = i, mu[i] = -1;
        for (register long long j = 1; j <= tot && prime[j] * i <= n; j++) {
            st[prime[j] * i] = 1;
            if (i % prime[j] != 0) mu[i * prime[j]] = -mu[i];
            else { mu[prime[j] * i] = 0; break; }
        }
    }
}

long long sum[N], cnt[N];
long long ans;

void work(long long x, long long y) {
	if (mu[x] == 0) return;
	ans += 1ll * mu[x] * (1ll * (y - 1) * 1ll * cnt[x] - sum[x]);
	sum[x] += y; cnt[x]++;
}

int main() {
	init(N - 10);
	scanf("%d", &n);
	for (long long i = 1; i <= n; i++) scanf("%d", &a[i]);
	sort(a + 1, a + 1 + n);
	for (long long i = 1; i <= n; i++) {
		for (long long j = 1; j * j <= a[i]; j++) {
			if (a[i] % j != 0) continue;
			work(j, i);
			if (j * j != a[i]) work(a[i] / j, i);
		}
	}
	printf("%lld\n", ans);
	return 0;
}

T13 CF235E Number Challenge

题面翻译

题意:记d(i)表示i的约数个数,输入a,b,c;
∑ i = 1 a ∑ j = 1 b ∑ k = 1 c d ( i j k ) m o d    ( 1073741824 ( 2 30 ) ) \sum_{i=1}^{a} \sum_{j=1}^{b} \sum_{k=1}^{c} d(ijk) \mod(1073741824(2^{30})) i=1aj=1bk=1cd(ijk)mod(1073741824(230)) 的值。

其中 d ( i ) d(i) d(i) 表示 i i i 的约数个数。


回顾 T8 P3327 【SDOI2015】约数个数和

其中给出了一个公式。这里拓展到三个数的情况:

将原公式修改为: ∑ i = 1 A ∑ j = 1 B ∑ k = 1 C ∑ a ∣ i ∑ b ∣ j ∑ c ∣ k [ gcd ⁡ ( a , b , c ) = = 1 ] \sum_{i=1}^{A} \sum_{j=1}^{B} \sum_{k=1}^{C} \sum_{a|i} \sum_{b|j} \sum_{c|k} [\gcd(a,b,c) == 1] i=1Aj=1Bk=1Caibjck[gcd(a,b,c)==1]

∑ a = 1 A ∑ b = 1 B ∑ c = 1 C ∑ a ∣ i ∑ b ∣ j ∑ c ∣ k [ gcd ⁡ ( a , b , c ) = = 1 ] \sum_{a=1}^{A} \sum_{b=1}^{B} \sum_{c=1}^{C} \sum_{a|i} \sum_{b|j} \sum_{c|k} [\gcd(a,b,c) == 1] a=1Ab=1Bc=1Caibjck[gcd(a,b,c)==1]

∑ a = 1 A ∑ b = 1 B ∑ c = 1 C ⌊ A a ⌋ ⌊ B b ⌋ ⌊ C c ⌋ [ gcd ⁡ ( a , b , c ) = = 1 ] \sum_{a=1}^{A} \sum_{b=1}^{B} \sum_{c=1}^{C} \lfloor \frac{A}{a} \rfloor \lfloor \frac{B}{b} \rfloor \lfloor \frac{C}{c} \rfloor [\gcd(a,b,c) == 1] a=1Ab=1Bc=1CaAbBcC[gcd(a,b,c)==1]

∑ i = 1 A ∑ j = 1 B ∑ k = 1 C ⌊ A i ⌋ ⌊ B j ⌋ ⌊ C k ⌋ [ gcd ⁡ ( i , j , k ) = = 1 ] \sum_{i=1}^{A} \sum_{j=1}^{B} \sum_{k=1}^{C} \lfloor \frac{A}{i} \rfloor \lfloor \frac{B}{j} \rfloor \lfloor \frac{C}{k} \rfloor [\gcd(i,j,k) == 1] i=1Aj=1Bk=1CiAjBkC[gcd(i,j,k)==1]

∑ i = 1 A ∑ j = 1 B ∑ k = 1 C ⌊ A i ⌋ ⌊ B j ⌋ ⌊ C k ⌋ ∑ d ∣ gcd ⁡ ( i , j ) μ ( d ) [ gcd ⁡ ( i , k ) = = 1 ] [ gcd ⁡ ( j , k ) = = 1 ] \sum_{i=1}^{A} \sum_{j=1}^{B} \sum_{k=1}^{C} \lfloor \frac{A}{i} \rfloor \lfloor \frac{B}{j} \rfloor \lfloor \frac{C}{k} \rfloor \sum_{d|\gcd(i,j)} \mu(d) [\gcd(i,k) == 1][\gcd(j,k) == 1] i=1Aj=1Bk=1CiAjBkCdgcd(i,j)μ(d)[gcd(i,k)==1][gcd(j,k)==1]

∑ k = 1 C ∑ d = 1 min ⁡ ( A , B ) μ ( d ) ∑ i = 1 ⌊ A d ⌋ ⌊ A i d ⌋ [ gcd ⁡ ( i d , k ) = = 1 ] ∑ j = 1 ⌊ B d ⌋ ⌊ B i d ⌋ [ gcd ⁡ ( j d , k ) = = 1 ] \sum_{k=1}^{C} \sum_{d=1}^{\min(A,B)} \mu(d) \sum_{i=1}^{\lfloor \frac{A}{d} \rfloor} \lfloor \frac{A}{id} \rfloor [\gcd(id,k) == 1] \sum_{j=1}^{\lfloor \frac{B}{d} \rfloor} \lfloor \frac{B}{id} \rfloor [\gcd(jd,k) == 1] k=1Cd=1min(A,B)μ(d)i=1dAidA[gcd(id,k)==1]j=1dBidB[gcd(jd,k)==1]

这个公式就是此题答案了。

在上代码之前有两个优化:

  1. 预处理 ∑ i = 1 ⌊ A d ⌋ ⌊ A i d ⌋ [ gcd ⁡ ( i d , k ) = = 1 ] \sum_{i=1}^{\lfloor \frac{A}{d} \rfloor} \lfloor \frac{A}{id} \rfloor [\gcd(id,k) == 1] i=1dAidA[gcd(id,k)==1] ∑ j = 1 ⌊ B d ⌋ ⌊ B i d ⌋ [ gcd ⁡ ( j d , k ) = = 1 ] \sum_{j=1}^{\lfloor \frac{B}{d} \rfloor} \lfloor \frac{B}{id} \rfloor [\gcd(jd,k) == 1] j=1dBidB[gcd(jd,k)==1]
  2. 用数组记录两个数是否互质。(即后面的判定条件 [ gcd ⁡ ( . . .   ,   . . . ) = = 1 ] [\gcd(...~,~...) == 1] [gcd(... , ...)==1]

代码:

#include <bits/stdc++.h>
using namespace std;
const long long N = 2e3 + 10;
const long long mod = 1073741824;

long long n, m;
long long tot, prime[N], mu[N], sum[N];
long long a, b, c;
bool st[N];

long long gcd(int a, int b)  // 欧几里得算法
{
    return b ? gcd(b, a % b) : a;
}

inline void init1(long long n) {
    sum[1] = mu[1] = 1;
    for (long long i = 2; i <= n; i++) {
        if (!st[i]) prime[++tot] = i, mu[i] = -1;
        for (long long j = 1; j <= tot && prime[j] * i <= n; j++) {
            st[prime[j] * i] = 1;
            if (i % prime[j] != 0) mu[i * prime[j]] = -mu[i];
            else { mu[prime[j] * i] = 0; break; }
        }
        sum[i] = sum[i - 1] + mu[i];
    }
}

long long A[N][N], B[N][N], have[N][N];

void init2() {
	for (long long i = 1; i <= 2000; i++)
		for (long long j = 1; j <= 2000; j++)
			if (gcd(i, j) == 1) have[i][j] = true;  //互质
	
	for (long long k = 1; k <= c; k++)
		for (long long d = 1; d <= min(a, b); d++) {
			for (long long i = 1; i <= a / d; i++)
				if (have[k][i * d]) //互质
					A[k][d] = (A[k][d] + a / (i * d)) % mod;
			for (long long i = 1; i <= b / d; i++)
				if (have[k][i * d]) //互质
					B[k][d] = (B[k][d] + b / (i * d)) % mod;
		}
}

int main() {
    scanf("%d%d%d", &a, &b, &c);
	init1(2000), init2();   //预处理
	long long ans = 0;
	for (long long k = 1; k <= c; k++) {    //公式中最外层循环
		long long res = 0;
		for (long long d = 1; d <= min(a, b); d++)
			res = (res + mu[d] % mod * A[k][d] * B[k][d] % mod) % mod;
		ans = (ans + res * (c / k) % mod) % mod;
	} 
	printf("%lld\n", (ans % mod + mod) % mod);
	return 0;
}

到这里这 13 13 13 题就结束了

公式有点多,我就写了大概两三周的样子。。。
第十三题是我生病的时候写的

b y e \Huge bye bye

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值