终于写完13题的题解了
用时 23 23 23 天
以此帖纪念洛谷红名
题单:
题单 \Huge \color{red}{题单} 题单
- P2568 GCD
- P2398 GCD SUM
- P1447 [NOI2010] 能量采集
- P2522 [HAOI2011]Problem b
- P4318 完全平方数
- P2257 YY的GCD
- P1829 [国家集训队]Crash的数字表格 / JZPTAB
- P3327 [SDOI2015]约数个数和
- P4449 于神之怒加强版
- P3312 [SDOI2014]数表
- P3704 [SDOI2017]数字表格
- CF1780F Three Chairs
- CF235E Number Challenge
T1 P2568 GCD
题目描述
给定正整数 n n n,求 1 ≤ x , y ≤ n 1\le x,y\le n 1≤x,y≤n 且 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] ∑p∈primes∑i=1n∑j=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] ∑p∈primes∑i=1⌊pn⌋∑j=1⌊pn⌋[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=1⌊pn⌋ϕ(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=1∑nj=1∑ngcd(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) k∣gcd(i,j) 的个数。
由 g g g 的定义, g k = ⌊ n k ⌋ 2 g_k = \lfloor \frac{n}{k} \rfloor ^ 2 gk=⌊kn⌋2
这两个 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=⌊kn⌋2−f2k−f3k−......
求 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=1n∑j=1mgcd(i,j))−n×m。
引理:欧拉反演。
n = ∑ d ∣ n ϕ ( d ) n = \sum_{d|n} \phi(d) n=d∣n∑ϕ(d)
现在重点只考虑 ∑ i = 1 n ∑ j = 1 m gcd ( i , j ) \sum_{i=1}^{n} \sum_{j=1}^{m} \gcd(i,j) ∑i=1n∑j=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=1n∑j=1m∑d∣gcd(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=1n∑j=1m[d∣gcd(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)⌊dn⌋⌊dm⌋
直接线性筛 ϕ \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 a≤x≤b, c ≤ y ≤ d c \le y \le d c≤y≤d,且 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=a∑bj=c∑d[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,c−1))−solve((1,a−1),(1,d))+solve((1,a−1),(1,c−1))
至此,完成了 1 − n , 1 − m 1-n,1-m 1−n,1−m的转化。只要求解这个子问题就行了。
P3455 [POI2007]ZAP-Queries
这题即是 T4 的子问题。
题目描述
密码学家正在尝试破解一种叫 BSA 的密码。
他发现,在破解一条消息的同时,他还需要回答这样一种问题:
给出 a , b , d a,b,d a,b,d,求满足 1 ≤ x ≤ a 1 \leq x \leq a 1≤x≤a, 1 ≤ y ≤ b 1 \leq y \leq b 1≤y≤b,且 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=1a∑i=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)=∑n∣kf(k)=⌊na⌋⌊nb⌋
根据莫反有: f ( k ) = ∑ n ∣ k μ ( ⌊ k n ⌋ ) F ( k ) f(k) = \sum_{n|k} \mu(\lfloor \frac{k}{n} \rfloor) F(k) f(k)=∑n∣kμ(⌊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=∑d∣kμ(⌊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=∑d∣kμ(⌊dk⌋)⌊ka⌋⌊kb⌋
设 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)⌊tda⌋⌊tdb⌋
至此,该子问题得到了解决,已经是 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)</