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]);
else
res += (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;
else
cur = 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;
}