Codeforces Round 1017 (Div. 4)题解
题目地址
https://codeforces.com/contest/2094
锐评
本次题目没什么好吐槽的。题意难度都挺好的,也没什么坑,比较符合D4预期。奈何自己太菜,想的有点慢,时间不够。F题构造题是我的弱项,想到个弱解被叉了。
题解
Problem A. Trippi Troppi
题目大意
Trippi Troppi 生活在一个奇异的世界。每个国家的古代名称由三个字符串组成。每个字符串的第一个字母连起来就是该国的现代名称。
给定国家的古代名称,请输出现代名称。
题解思路:模拟
直接按照题意输出即可。时间复杂度为 O ( 1 ) O(1) O(1) 。
参考代码(C++)
#include <bits/stdc++.h>
using namespace std;
string sa, sb, sc;void solve() {cin >> sa >> sb >> sc;cout << sa[0] << sb[0] << sc[0] << '\n';
}int main() {ios::sync_with_stdio(false);cin.tie(nullptr);cout.tie(nullptr);int t = 1;cin >> t;while (t--)solve();return 0;
}
Problem B. Bobritto Bandito
题目大意
在波布里托-班迪托居住的小镇上,在一条无穷数线上有无穷多的房子,房子的位置是 … , − 2 , − 1 , 0 , 1 , 2 , … \ldots, -2, -1, 0, 1, 2, \ldots …,−2,−1,0,1,2,… 。在 0 0 0 天,他让 0 0 0 号房子里不幸的居民感染了瘟疫。接下来的每一天,瘟疫都会传播到恰好1个健康的家庭,而这些家庭恰好与一个受感染的家庭相邻。可以看出,每天受感染的房屋形成一个连续的片段。
假设从第 l l l 户开始到第 r r r 户结束的线段表示为 [ l , r ] [l, r] [l,r] 。你知道在 n n n 天后, [ l , r ] [l, r] [l,r] 段被感染了。请找出可能在第 m m m 天( m ≤ n m \leq n m≤n )被感染的线段 [ l ′ , r ′ ] [l', r'] [l′,r′] 。
题解思路:数学
直接判断相差了几天,左/右半段缩短即可。但是要注意, 0 0 0 是一定要包含的。时间复杂度为 O ( 1 ) O(1) O(1) 。
参考代码(C++)
#include <bits/stdc++.h>
using namespace std;
int n, m, l, r;void solve() {cin >> n >> m >> l >> r;int cnt = n - m;int ct = min(r, cnt);r -= ct;cnt -= ct;l += cnt;cout << l << ' ' << r << '\n';
}int main() {ios::sync_with_stdio(false);cin.tie(nullptr);cout.tie(nullptr);int t = 1;cin >> t;while (t--)solve();return 0;
}
Problem C. Brr Brrr Patapim
题目大意
Brr Brrr 帕塔皮姆正在努力学习提拉米苏的秘密密码,它是 2 ⋅ n 2 \cdot n 2⋅n 个元素的排列组合。为了帮助帕塔皮姆猜测,提拉米苏给了他一个 n × n n \times n n×n 网格 G G G ,其中 G i , j G_{i,j} Gi,j (或者说网格中第 i i i 行第 j j j 列中的元素)包含 p i + j p_{i+j} pi+j ,或者说排列组合中的第 ( i + j ) (i+j) (i+j) 个元素。
给定这个网格,请帮助帕塔皮姆破解被遗忘的密码。可以保证这个排列是存在的,而且可以证明这个排列是唯一确定的。
m m m 个整数的排列是 m m m 个整数的序列,其中都恰好包含 1 , 2 , … , m 1, 2, \ldots, m 1,2,…,m 一次。例如, [ 1 , 3 , 2 ] [1, 3, 2] [1,3,2] 和 [ 2 , 1 ] [2, 1] [2,1] 是排列,而 [ 1 , 2 , 4 ] [1, 2, 4] [1,2,4] 和 [ 1 , 3 , 2 , 3 ] [1, 3, 2, 3] [1,3,2,3] 不是排列。
题解思路:模拟/构造
按照题意直接模拟构造,没构造出来的位置,补充即可。时间复杂度为 O ( n 2 l o g n ) O(n^2logn) O(n2logn) (因为我用了set,其实不用,序列是唯一的,所以可降为 O ( n 2 ) O(n^2) O(n2) )。
PS:其实只需要按照从左上角往右走7字型,然后从第2个位置开始填充即可,第1个位置直接可以算出来(详见第二份代码)。
参考代码(C++)
复杂度为 O ( n 2 l o g n ) O(n^2logn) O(n2logn) 版本代码。
#include <bits/stdc++.h>
using namespace std;
const int maxn = 1'605;
int a[maxn][maxn], b[maxn];
int n;void solve() {cin >> n;int m = n << 1;set<int> st;for (int i = 1; i <= m; ++i) {st.insert(i);b[i] = -1;}for (int i = 1; i <= n; ++i)for (int j = 1; j <= n; ++j) {cin >> a[i][j];b[i + j] = a[i][j];st.erase(b[i + j]);}for (int i = 1; i <= m; ++i)if (b[i] == -1) {b[i] = *st.begin();st.erase(st.begin());}for (int i = 1; i <= m; ++i)cout << b[i] << (" \n"[i == m]);
}int main() {ios::sync_with_stdio(false);cin.tie(nullptr);cout.tie(nullptr);int t = 1;cin >> t;while (t--)solve();return 0;
}
复杂度为 O ( n 2 ) O(n^2) O(n2) 版本代码。
#include <bits/stdc++.h>
using namespace std;
const int maxn = 1'605;
int a[maxn];
int n;void solve() {cin >> n;int m = n << 1, id = 1, sum = 0, x;for (int i = 0; i < n; ++i)for (int j = 0; j < n; ++j) {cin >> x;if (i == 0 || j == n - 1) {a[id++] = x;sum += x;}}a[0] = (((1 + m) * m) >> 1) - sum;for (int i = 0; i < m; ++i)cout << a[i] << (" \n"[i == m - 1]);
}int main() {ios::sync_with_stdio(false);cin.tie(nullptr);cout.tie(nullptr);int t = 1;cin >> t;while (t--)solve();return 0;
}
Problem D. Tung Tung Sahur
题目大意
你面前有两面鼓:一面是左鼓,一面是右鼓。敲击左边的鼓可以记录为 “L”,敲击右边的鼓可以记录为 “R”。
统治这个世界的奇异力量是善变的:有时敲击一次听到了一次,有时敲击一次却听到了两次。因此,敲击左边鼓的声音可能是 “L” 或 “LL”,敲击右边鼓的声音可能是 “R” 或 “RR”。
敲击的顺序记录在字符串 p p p 中,听到的声音记录在字符串 s s s 中。给定 p p p 和 s s s ,判断字符串 s s s 是否可能是字符串 p p p 敲击的结果。
例如,如果 $p = $ “LR”,那么敲击的结果可能是 “LR”、“LRR”、“LLR” 和 “LLRR” 中的任何一个,但字符串 “LLLR” 或 “LRL” 则不可能。
题解思路:双指针
按照题意,对于 p p p 中每个 “L”/“R” , s s s 中必须有至少一个、至多两个同样的字符与之对应。因此我们只需要遍历 p p p ,然后看 s s s 中是否有与之对应的字符满足条件即可。因为 p p p 中 “L” 不可能对应 s s s 中的 “R” ,反之亦然,因此我们每次判断连续相同的字符段即可。时间复杂度为 O ( n + m ) O(n + m) O(n+m) 。
参考代码(C++)
#include <bits/stdc++.h>
using namespace std;
const int maxn = 1'605;
string p, str;
int n, m;void solve() {cin >> p >> str;int n = p.size(), m = str.size();int i = 0, j = 0;while (i < n) {if (j == m || p[i] != str[j]) {cout << "NO\n";return;}int k1 = i + 1, k2 = j + 1;while (k1 < n && p[k1] == p[i])++k1;while (k2 < m && str[k2] == str[j])++k2;int ca = k1 - i, cb = k2 - j;if (ca > cb || cb > (ca << 1)) {cout << "NO\n";return;}i = k1;j = k2;}cout << (j == m ? "YES\n" : "NO\n");
}int main() {ios::sync_with_stdio(false);cin.tie(nullptr);cout.tie(nullptr);int t = 1;cin >> t;while (t--)solve();return 0;
}
Problem E. Boneca Ambalabu
题目大意
博尼卡-安巴拉布给你一个由 n n n 个整数 a 1 , a 2 , … , a n a_1, a_2, \ldots, a_n a1,a2,…,an 组成的序列。
请输出所有 1 ≤ k ≤ n 1 \leq k \leq n 1≤k≤n 中 ( a k ⊕ a 1 ) + ( a k ⊕ a 2 ) + … + ( a k ⊕ a n ) (a_k \oplus a_1) + (a_k \oplus a_2) + \ldots + (a_k \oplus a_n) (ak⊕a1)+(ak⊕a2)+…+(ak⊕an) 的最大值。注意 ⊕ \oplus ⊕ 表示 bitwise XOR 运算。
题解思路:枚举+位运算
根据题目给定计算公式以及 ⊕ \oplus ⊕ 性质,我们只需要知道,在二进制状态下,某一位的结果与其对应的其他数该位情况相关,并且与该位分别有多少个数相关,有多少个数答案就是几倍。因为我们只需要计数每一位对应多少个数,然后枚举序列每个数,计算得到的结果取最大即可。时间复杂度为 O ( n ) O(n) O(n) (实际为 30 n 30n 30n ,忽略常数,但运行时间需要把 30 30 30 考虑在内)。
参考代码(C++)
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
const int maxn = 200'005;
int a[maxn], cnt[30];
int n;void solve() {cin >> n;for (int i = 0; i < 30; ++i)cnt[i] = 0;for (int i = 0; i < n; ++i) {cin >> a[i];for (int j = 0; j < 30; ++j)if ((a[i] >> j) & 1)++cnt[j];}ll ans = 0;for (int i = 0; i < n; ++i) {ll res = 0;for (int j = 0; j < 30; ++j) {int mask = (a[i] >> j) & 1;if (mask)res += (1LL << j) * (n - cnt[j]);elseres += (1LL << j) * cnt[j];}ans = max(ans, res);}cout << ans << '\n';
}int main() {ios::sync_with_stdio(false);cin.tie(nullptr);cout.tie(nullptr);int t = 1;cin >> t;while (t--)solve();return 0;
}
Problem F. Trulimero Trulicina
题目大意
楚里西纳给出整数 n n n 、 m m m 和 k k k 。可以保证 k ≥ 2 k \geq 2 k≥2 和 n ⋅ m ≡ 0 ( m o d k ) n \cdot m \equiv 0 \pmod{k} n⋅m≡0(modk) 。
输出一个 n n n 乘以 m m m 的整数网格,使得下列条件均成立:
- 网格中的每个整数都在 1 1 1 和 k k k 之间(包括首尾两个整数)。
- 从 1 1 1 到 k k k 的每个整数出现的次数相同。
- 共享边的两个单元格中没有相同的整数。
可以证明这样的网格总是存在的。如果有多个解,请输出任意一个。
题解思路:构造
我们会想当然地采取循环形式,如 [ 1 , 2 , … , k − 1 , k , 1 , 2 , … , k − 1 , k , 1 , 2 , … , k − 1 , k ] [1, 2, \ldots, k - 1, k, 1, 2, \ldots, k - 1, k, 1, 2, \ldots, k - 1, k] [1,2,…,k−1,k,1,2,…,k−1,k,1,2,…,k−1,k] ,然后从左往右从上往下依次填入网格。这样,网格左右肯定不同,相差1个数。那么上下是否相同呢?假如上面为 x x x ,那么根据上面的填充方式,下面为 y = ( x + m ) m o d k y = (x + m) \bmod k y=(x+m)modk ,那么只要 m m o d k m \bmod k mmodk 不等于零,那么 y y y 肯定在 x x x 右侧,上下也是不同的,符合题意。但如果恰好等于零,怎么办呢?这种情况相当于每行都是序列 [ 1 , 2 , … , k − 1 , k ] [1, 2, \ldots, k - 1, k] [1,2,…,k−1,k] 重复 m k \frac{m}{k} km 次,即每一行完全一样。既然这样,我把第2行往左边循环移位一个位置,那么不就刚好错开了吗?是的,这样上下也就相差了1个数。以此类推,第4,6,8等等偶数行也错开不就满足条件了。
综上所述,问题得解。时间复杂度为 O ( n m ) O(nm) O(nm) 。
参考代码(C++)
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
const int maxn = 200'005;
vector<int> adj[maxn];
int n, m, k;void calc(int n, int m, int k) {bool f = m % k == 0;int cur = 0;for (int i = 0; i < n; ++i) {adj[i].clear();if (f) {if (i & 1)cur = 1;elsecur = 0;}for (int j = 0; j < m; ++j) {adj[i].push_back(cur + 1);cur = (cur + 1) % k;}}for (int i = 0; i < n; ++i)for (int j = 0; j < m; ++j)cout << adj[i][j] << (" \n"[j == m - 1]);
}void solve() {cin >> n >> m >> k;calc(n, m, k);
}bool check(int n, int m) {for (int i = 0; i < n; ++i)for (int j = 0; j < m; ++j) {if (i && adj[i][j] == adj[i - 1][j])return true;if (j && adj[i][j] == adj[i][j - 1])return true;}return false;
}void test() {for (int i = 1; i < 30; ++i)for (int j = 1; j < 30; ++j)for (int k = 2; k * k <= i * j; ++k)if (i * j % k == 0) {calc(i, j, k);if (check(i, j))cout << i << ' ' << j << ' ' << k << '\n';}
}int main() {ios::sync_with_stdio(false);cin.tie(nullptr);cout.tie(nullptr);// test();int t = 1;cin >> t;while (t--)solve();return 0;
}
Problem G. Chimpanzini Bananini
题目大意
奇潘齐尼-巴纳尼尼站在了一场重大战役的边缘,这场战役注定会带来最终的胜利。
对于长度为 m m m 的任意数组 b b b ,我们把数组的粗糙度记为 ∑ i = 1 m b i ⋅ i = b 1 ⋅ 1 + b 2 ⋅ 2 + b 3 ⋅ 3 + … + b m ⋅ m \sum_{i=1}^m{b_i \cdot i} = b_1 \cdot 1 + b_2 \cdot 2 + b_3 \cdot 3 + \ldots + b_m \cdot m ∑i=1mbi⋅i=b1⋅1+b2⋅2+b3⋅3+…+bm⋅m 。
Chimpanzini Bananini 送给你一个空数组。你可以对它进行三种操作。
- 对数组进行循环移位。也就是说,数组 [ a 1 , a 2 , … , a n ] [a_1, a_2, \ldots, a_n] [a1,a2,…,an] 变成 [ a n , a 1 , a 2 , … , a n − 1 ] [a_n, a_1, a_2, \ldots, a_{n-1}] [an,a1,a2,…,an−1] 。
- 反转整个数组。即数组 [ a 1 , a 2 , … , a n ] [a_1, a_2, \ldots, a_n] [a1,a2,…,an] 变成 [ a n , a n − 1 , … , a 1 ] [a_n, a_{n-1}, \ldots, a_1] [an,an−1,…,a1] 。
- 在数组末尾添加一个元素。在数组的末尾添加 k k k 之后,数组 [ a 1 , a 2 , … , a n ] [a_1, a_2, \ldots, a_n] [a1,a2,…,an] 变为 [ a 1 , a 2 , … , a n , k ] [a_1, a_2, \ldots, a_n, k] [a1,a2,…,an,k] 。
每次操作后,您都想计算数组的粗糙度。
请注意,所有操作都是持久的。这意味着每次操作都会修改数组,后续操作应在前一次操作后应用于数组的当前状态。
题解思路:双端队列+数学
我们来一个操作一个操作地分析。我们先假设操作前的粗糙度为 f ( n ) = d f(n) = d f(n)=d ,对于操作一,操作后粗糙度如下。
f ′ ( n ) = a n ⋅ 1 + a 1 ⋅ 2 + a 2 ⋅ 3 + … + a n − 1 ⋅ n = a n ⋅ 1 + a 1 + a 2 + ⋯ + a n − 1 + a 1 ⋅ 1 + a 2 ⋅ 2 + … + a n − 1 ⋅ ( n − 1 ) f'(n) = a_n \cdot 1 + a_1 \cdot 2 + a_2 \cdot 3 + \ldots + a_{n - 1} \cdot n = a_n \cdot 1 + a_1 + a_2 + \cdots + a_{n - 1} + a_1 \cdot 1 + a_2 \cdot 2 + \ldots + a_{n - 1} \cdot (n - 1) f′(n)=an⋅1+a1⋅2+a2⋅3+…+an−1⋅n=an⋅1+a1+a2+⋯+an−1+a1⋅1+a2⋅2+…+an−1⋅(n−1)
化简后可得。
f ′ ( n ) = ∑ i = 1 n − 1 a i + d − a n ⋅ ( n − 1 ) f'(n) = \sum_{i = 1}^{n - 1}{a_i} + d - a_n \cdot (n - 1) f′(n)=i=1∑n−1ai+d−an⋅(n−1)
看起来我们只需要知道当前数组的和 s u m sum sum 、最后一个元素 a n a_n an 以及粗糙度 d d d 就能快速计算出 f ′ ( n ) = d + s u m − a n ⋅ n f'(n) = d + sum - a_n \cdot n f′(n)=d+sum−an⋅n 。那么支持从尾部删除和头部插入的数据结构用什么呢?显然,双端队列即可。
对于操作二,翻转整个数组每次都要 O ( n ) O(n) O(n) 时间复杂度,显然不可接受。那么怎么办呢?基于操作一的灵感,我们倒着维护一份一模一样的双端队列是不是可以呢?显然是可行的,这样当翻转时,我们切换一下当前双端队列即可,只需要 O ( 1 ) O(1) O(1) 时间复杂度,可以接受。那么对于操作一,此队列(形如 [ a n , a n − 1 , ⋯ , a 2 , a 1 ] [a_n, a_{n - 1}, \cdots, a_2, a_1] [an,an−1,⋯,a2,a1] )的粗糙度如何变化呢?我们假设操作前的粗糙度为 g ( n ) = d ′ g(n) = d' g(n)=d′ 、当前数组的和为 s u m sum sum (和肯定是一样的),那么操作一相当于把这里的 a n a_n an 放到队列的尾部,操作后粗糙度如下。
g ′ ( n ) = a n − 1 ⋅ 1 + ⋯ + a 2 ⋅ ( n − 2 ) + a 1 ⋅ ( n − 1 ) + a n ⋅ n = a n − 1 ⋅ 2 + ⋯ + a 2 ⋅ ( n − 1 ) + a 1 ⋅ n − a n − 1 − ⋯ − a 2 − a 1 + a n ⋅ n g'(n) = a_{n - 1} \cdot 1 + \cdots + a_2 \cdot (n - 2) + a_1 \cdot (n - 1) + a_n \cdot n = a_{n - 1} \cdot 2 + \cdots + a_2 \cdot (n - 1) + a_1 \cdot n - a_{n - 1} - \cdots - a_2 - a_1 + a_n \cdot n g′(n)=an−1⋅1+⋯+a2⋅(n−2)+a1⋅(n−1)+an⋅n=an−1⋅2+⋯+a2⋅(n−1)+a1⋅n−an−1−⋯−a2−a1+an⋅n
化简后可得。
g ′ ( n ) = d ′ − a n ⋅ 1 − ∑ i = 1 n − 1 a i + a n ⋅ n = d ′ + a n ⋅ n − s u m g'(n) = d' - a_n \cdot 1 - \sum_{i = 1}^{n - 1}{a_i} + a_n \cdot n = d' + a_n \cdot n - sum g′(n)=d′−an⋅1−i=1∑n−1ai+an⋅n=d′+an⋅n−sum
对于操作三,添加一个元素 x x x ,相当于往正向队列尾部添加一个元素,往逆向队列头部添加一个元素。根据上面分析,正向队列粗糙度为 f ( n ) f(n) f(n) 、逆向队列粗糙度为 g ( n ) g(n) g(n) 以及数组元素和为 s u m sum sum ,那么有如下公式。
f ( n ) = a 1 ⋅ 1 + a 2 ⋅ 2 + ⋯ + a n ⋅ n f(n) = a_1 \cdot 1 + a_2 \cdot 2 + \cdots + a_n \cdot n f(n)=a1⋅1+a2⋅2+⋯+an⋅n
g ( n ) = a n ⋅ 1 + ⋯ + a 2 ⋅ ( n − 1 ) + a 1 ⋅ n g(n) = a_n \cdot 1 + \cdots + a_2 \cdot (n - 1) + a_1 \cdot n g(n)=an⋅1+⋯+a2⋅(n−1)+a1⋅n
s u m = a 1 + a 2 + ⋯ + a n sum = a_1 + a_2 + \cdots + a_n sum=a1+a2+⋯+an
操作后,有如下公式。
f ′ ( n ) = a 1 ⋅ 1 + a 2 ⋅ 2 + ⋯ + a n ⋅ n + x ⋅ ( n + 1 ) = f ( n ) + x ⋅ ( n + 1 ) f'(n) = a_1 \cdot 1 + a_2 \cdot 2 + \cdots + a_n \cdot n + x \cdot (n + 1) = f(n) + x \cdot (n + 1) f′(n)=a1⋅1+a2⋅2+⋯+an⋅n+x⋅(n+1)=f(n)+x⋅(n+1)
g ′ ( n ) = x ⋅ 1 + a n ⋅ 2 + ⋯ + a 2 ⋅ n + a 1 ⋅ ( n + 1 ) = g ( n ) + s u m + x g'(n) = x \cdot 1 + a_n \cdot 2 + \cdots + a_2 \cdot n + a_1 \cdot (n + 1) = g(n) + sum + x g′(n)=x⋅1+an⋅2+⋯+a2⋅n+a1⋅(n+1)=g(n)+sum+x
s u m ′ = s u m + x sum' = sum + x sum′=sum+x
综上所述,我们按照分析模拟即可。以上每个操作时间复杂度均为 O ( 1 ) O(1) O(1) ,因此整体时间复杂度为 O ( q ) O(q) O(q) 。
参考代码(C++)
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
const int maxn = 100'005;
int q;void solve() {cin >> q;deque<int> dq[2];ll sum = 0, ans[2] = {0};int cur = 0, op, x;while (q--) {cin >> op;if (op == 3) {cin >> x;dq[cur].push_back(x);sum += x;ans[cur] += 1LL * dq[cur].size() * x;dq[cur ^ 1].push_front(x);ans[cur ^ 1] += sum;} else if (op == 2)cur ^= 1;else if (!dq[cur].empty()) {int siz = dq[cur].size();x = dq[cur].back();// cout << "siz:" << siz << "; x:" << x << '\n';dq[cur].pop_back();dq[cur].push_front(x);ans[cur] += sum - 1LL * siz * x;dq[cur ^ 1].pop_front();dq[cur ^ 1].push_back(x);ans[cur ^ 1] += 1LL * siz * x - sum;}cout << ans[cur] << '\n';}
}int main() {ios::sync_with_stdio(false);cin.tie(nullptr);cout.tie(nullptr);int t = 1;cin >> t;while (t--)solve();return 0;
}
Problem H. La Vaca Saturno Saturnita
题目大意
Saturnita 的情绪取决于一个长度为 n n n 的数组 a a a 和一个只有他知道如何计算的函数 f ( k , a , l , r ) f(k, a, l, r) f(k,a,l,r) 。下面是他的函数 f ( k , a , l , r ) f(k, a, l, r) f(k,a,l,r) 的伪代码。
int ans = 0;
for (int i = l; i <= r; ++i) {while (k % a[i] == 0)k /= a[i];ans += k;
}
cout << ans << '\n';
给你 q q q 个查询,每个查询都包含整数 k k k 、 l l l 和 r r r 。请为每个查询输出 f ( k , a , l , r ) f(k, a, l, r) f(k,a,l,r) 。
题解思路:枚举+二分+数论
**注意此题时限是4s。**由于 1 ≤ q ≤ 5 ⋅ 1 0 4 1 \leq q \leq 5 \cdot 10^4 1≤q≤5⋅104 、 1 ≤ k ≤ 1 0 5 1 \leq k \leq 10^5 1≤k≤105 ,显然我们依据数论知识可以用时间复杂度为 O ( k ) O(\sqrt{k}) O(k) 的方法算出 k k k 的所有因子。
观察上述伪代码,对于每个因子,其会一直除 k k k ,直到除不尽。那么对于区间 [ l , r ] [l, r] [l,r] 内处理后的 k k k 是怎么样的呢?它一定是形如 [ k 1 , k 1 , … , k 2 , k 2 , … , k m , k m , … ] [k1, k1, \ldots, k2, k2, \ldots, k_m, k_m, \ldots] [k1,k1,…,k2,k2,…,km,km,…] 这样的。那么什么时候 k k k 才会变化呢?显然遇到一个因子就会变化。因此,我们只需要找到区间 [ l , r ] [l, r] [l,r] 内所有的因子,按位置从小到大枚举出所有的 k i k_i ki 即可,只需要记录上一个位置在哪里即可得到段的长度 l i l_i li ,答案就是 ∑ i = 1 m k i ⋅ l i \sum_{i = 1}^m{k_i \cdot l_i} ∑i=1mki⋅li 。
根据上面的分析,因子很容易计算,那么怎么找到合适的因子呢?我们只需要把题目给定因子的所有位置按照从小到大顺序存起来,就可以用二分来查找处于区间 [ l , r ] [l, r] [l,r] 的首个位置。
综上所述,问题得解。时间复杂度为 O ( q k l o g n ) O(q \sqrt{k} logn) O(qklogn) 。
参考代码(C++)
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
const int maxn = 100'005;
int a[maxn];
int n, q;void solve() {cin >> n >> q;map<int, vector<int>> adj;for (int i = 0; i < n; ++i) {cin >> a[i];adj[a[i]].push_back(i);}int k, l, r;while (q--) {cin >> k >> l >> r;--l, --r;if (k == 1)cout << (r - l + 1) << '\n';else {vector<int> vi, vp;for (int i = 2; i * i <= k; ++i)if (k % i == 0) {vi.push_back(i);int j = k / i;if (j != i)vi.push_back(j);}vi.push_back(k);for (int& x : vi)if (adj.count(x)) {auto& v = adj[x];auto it = lower_bound(v.begin(), v.end(), l);if (it != v.end() && *it <= r)vp.push_back(*it);}sort(vp.begin(), vp.end());ll ans = 0;int last = l;for (int& id : vp) {int x = k;while (x % a[id] == 0)x /= a[id];int cnt = id - last;ans += 1LL * cnt * k;last = id;k = x;}ans += 1LL * k * (r + 1 - last);cout << ans << '\n';}}
}int main() {ios::sync_with_stdio(false);cin.tie(nullptr);cout.tie(nullptr);int t = 1;cin >> t;while (t--)solve();return 0;
}