题目链接
题目大意
Definition1
我们认为非空数组 a a a 是好的,当且仅当 a a a 满足下列其中一个条件:
- ∣ a ∣ = 1 |a| = 1 ∣a∣=1;
- ∀ i ∈ [ 1 , ∣ a ∣ ] \forall i \in [1, \ |a|] ∀i∈[1, ∣a∣],一定存在 j ∈ [ 1 , i ) ∪ ( i , ∣ a ∣ ] j \in [1, \ i) \cup (i, \ |a|] j∈[1, i)∪(i, ∣a∣],有 a i ∣ a j a_i \mid a_j ai∣aj 或 a j ∣ a i a_j \mid a_i aj∣ai。
其中 ∣ a ∣ |a| ∣a∣ 表示数组 a a a 的长度, x ∣ y x \mid y x∣y 表示 x x x 整除 y y y,例如 2 2 2 能整除 4 4 4。
Definition2
我们定义数组 a a a 是极好的,当且仅当 a a a 的任意非空连续子数组都是好的。
例如连续子数组 a [ l . . . r ] a[l...r] a[l...r] 表示 a l , a l + 1 , ⋯ , a r a_l, \ a_{l + 1}, \ \cdots \ , \ a_r al, al+1, ⋯ , ar,其中 1 ⩽ l ⩽ r ⩽ ∣ a ∣ 1 \leqslant l \leqslant r \leqslant |a| 1⩽l⩽r⩽∣a∣。
Definition3
我们定义数组 a a a 加上整数 x x x 的结果为 a ′ a' a′,即令 a ′ = a + x a' = a + x a′=a+x, a ′ a' a′ 需要满足:
- ∣ a ′ ∣ = ∣ a ∣ |a'| = |a| ∣a′∣=∣a∣;
- 对 ∀ i ∈ [ 1 , ∣ a ∣ ] \forall i \in [1, \ |a|] ∀i∈[1, ∣a∣],均有 a i ′ = a i + x a'_i = a_i + x ai′=ai+x。
Question
共 T T T 组数据,每组数据给定一个数组 a a a 和一个正整数 k k k,求有多少整数 x ∈ [ 1 , k ] x \in [1, \ k] x∈[1, k] 满足 a + x a + x a+x 是极好的数组。
数据范围
- 1 ⩽ ∣ a ∣ ⋅ T ⩽ 6 × 1 0 4 1 \leqslant |a| \cdot T \leqslant 6 \times 10^4 1⩽∣a∣⋅T⩽6×104;
- 1 ⩽ a i , k ⩽ 1 0 9 1 \leqslant a_i, \ k \leqslant 10^9 1⩽ai, k⩽109。
Solution
根据定义,我们可以得到数组 a a a 是极好的 的充要条件:对 ∀ i ∈ [ 1 , ∣ a ∣ ) \forall i \in [1, |a|) ∀i∈[1,∣a∣),均有 a i ∣ a i + 1 a_i \mid a_{i + 1} ai∣ai+1 或 a i + 1 ∣ a i a_{i + 1} \mid a_i ai+1∣ai。
下面来证明一下。
充分性( ⇐ \Leftarrow ⇐):
首先,数组
a
a
a 只要满足
∣
a
∣
=
1
|a| = 1
∣a∣=1 就是好的,也是极好的。
再设
a
[
l
.
.
.
r
]
a[l...r]
a[l...r] 这个子数组是好的,由上述条件,可以推出
a
[
l
.
.
.
(
r
+
1
)
]
a[l...(r + 1)]
a[l...(r+1)]是好的,因为
a
r
∣
a
r
+
1
a_r \mid a_{r + 1}
ar∣ar+1 或
a
r
+
1
∣
a
r
a_{r + 1} \mid a_r
ar+1∣ar。
由数学归纳法,
a
a
a 的任意连续子数组是好的,那么
a
a
a 就是极好的。
必要性( ⇒ \Rightarrow ⇒):
若数组 a a a 是极好的,可以得到 a a a 的任意连续子数组是好的,那么长度为 2 2 2 的连续子数组也是好的,换句话说就是,对 ∀ i ∈ [ 1 , ∣ a ∣ ) \forall i \in [1, \ |a|) ∀i∈[1, ∣a∣),都有 a i ∣ a i + 1 a_i \mid a_{i + 1} ai∣ai+1 或 a i + 1 ∣ a i a_{i + 1} \mid a_i ai+1∣ai(根据好数组的定义)。
有了这个充要条件,我们就可以 O ( ∣ a ∣ ) O(|a|) O(∣a∣) 判断数组 a a a 是否是极好的。
但现在要判断的是 a + x a + x a+x 是否极好,暴力枚举是 O ( 1 0 9 ∣ a ∣ ) O(10^9|a|) O(109∣a∣),因此还需要进一步挖掘性质。
首先,如果 a a a 的所有元素都相同,那么 a a a 一定是极好的,且 a + x a + x a+x 也一定是极好的,这种情况下 x x x 有 k k k 种选法。
下面考虑 a a a 存在不同元素的情况。
根据充要条件,我们需要对相邻两项的整除关系做限制,所以我们可以先考虑用其中一个 a i a_i ai 和 a i + 1 a_{i + 1} ai+1 来限制 x x x 的选取。下面进行详细说明。
令 a ′ = a + x a' = a + x a′=a+x,若 a ′ a' a′ 是极好的,说明任意一个长度为 2 2 2 的连续子数组都是好的,且由于 a a a 存在不同元素,所以我们一定可以在某个 i ∈ [ 1 , ∣ a ∣ ) i \in [1, \ |a|) i∈[1, ∣a∣) 取这样两个数:
- 令 u = min ( a i , a i + 1 ) , v = max ( a i , a i + 1 ) u = \min(a_i, \ a_{i + 1}), \ v = \max(a_i, \ a_{i + 1}) u=min(ai, ai+1), v=max(ai, ai+1),满足 u < v u < v u<v。
由 a ′ a' a′ 是极好的可以得到 ( u + x ) ∣ ( v + x ) (u + x) \mid (v + x) (u+x)∣(v+x),那么就有 ( v + x ) = t ( u + x ) ( t > 1 ) (v + x) = t(u + x) (t > 1) (v+x)=t(u+x)(t>1),那么就有 v − u = ( t − 1 ) ( u + x ) v - u = (t - 1)(u + x) v−u=(t−1)(u+x),令 d = v − u d = v - u d=v−u,会得到 ( u + x ) ∣ d (u + x) \mid d (u+x)∣d,即 u u u 加上一个 x x x 后是 d d d 的约数。
反过来说,我们可以找到这样的 u , v u, \ v u, v,然后枚举 d = v − u d = v - u d=v−u 的约数 d i v div div(即上一段的 ( u + x ) (u + x) (u+x)),看其是否满足 d i v > u div > u div>u 且 d i v ⩽ u + k div \leqslant u + k div⩽u+k,如果是,那说明 x = d i v − u x = div - u x=div−u,再用上面的充要条件 O ( n ) O(n) O(n) 判断 a + x a + x a+x 是否是极好的。
时间复杂度 O ( n V ) O(n\sqrt{V}) O(nV)
- 其中 V V V 为值域范围,题中 V = 1 0 9 V = 10^9 V=109;
- 但其实复杂度远远达不到 V \sqrt{V} V,因为对于 1 0 9 10^9 109 内的数,其约数个数最大为 1344 1344 1344;
- 总复杂度跑满的话计算量大约是 1 0 8 10^8 108。
C++ Code
#include <bits/stdc++.h>
using i64 = int64_t;
void solve() {
int n, k;
std::cin >> n >> k;
std::vector<int> a(n);
for (int i = 0; i < n; i++) {
std::cin >> a[i];
}
int u = -1;
int v = -1;
for (int i = 0; i + 1 < n; i++) {
if (a[i] != a[i + 1]) {
u = std::min(a[i], a[i + 1]);
v = std::max(a[i], a[i + 1]);
break;
}
}
int d = v - u;
if (d == 0) {
std::cout << k << " " << k * (k + 1LL) / 2 << "\n";
return;
}
int cnt = 0;
i64 sum = 0;
auto work = [&](int x) {
x -= u;
if (x <= 0 or x > k) {
return;
}
auto b = a;
for (int &y: b) {
y += x;
}
for (int i = 1; i < n; i++) {
if (std::max(b[i], b[i - 1]) % std::min(b[i], b[i - 1])) {
return;
}
}
cnt++;
sum += x;
};
for (int i = 1; i * i <= d; i++) {
if (d % i == 0) {
work(i);
if (i * i != d) {
work(d / i);
}
}
}
std::cout << cnt << " " << sum << "\n";
}
int main() {
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
int T = 1;
std::cin >> T;
while (T--) {
solve();
}
return 0;
}