每周至少五篇博客:2/5
https://codeforces.com/contest/1305/problem/F
题意
Kuroni对其他设置者以将他作为主题感到非常生气!作为惩罚,他强迫他们解决以下问题:
您的数组 a a a 由 n n n 正整数组成。一个操作包括选择一个元素,然后向其添加 1 1 1 ,或从中减去 1 1 1 ,以使元素保持积极。我们说,如果其所有元素的最大共同除数不是 1 1 1 ,则数组是好的。找到使阵列良好所需的最小操作数量。
无法与Kuroni的智力相匹配,Setter无法解决问题。帮助他们摆脱Kuroni的惩罚!
思路
关于随机化的题目,有道类似的题目推荐看一下:为美好的XCPC献上典题——AtCoder Beginner Contest 272 G - Yet Another mod M(随机化)
首先分析下答案的上界——答案不会超过 n n n ,我们设数组中有 k k k 个奇数,那么我们只需要将这 k k k 个奇数都 + 1 +1 +1 ,就可以使得整个数组都是偶数了,那么整个数组一定有 2 2 2 这个公因数,其中 0 ≤ k ≤ n 0\le k \le n 0≤k≤n ,所以答案不会超过 n n n
由上述结论可知,至少有一半的元素最多进行一次操作。反命题至少有一半以上的元素至少进行了两次操作,此时的操作数至少是 ( n 2 + 1 ) × 2 > n (\frac n 2 + 1) \times 2 > n (2n+1)×2>n,与上述结论相悖
实际上发现至少有一半的元素就可以往随机化上想了
我们可以直接随机一个元素,那么这个元素有
n
2
\frac n 2
2n 的概率是最多进行一次操作的,那么分别对
−
1
,
+
0
,
+
1
-1, +0, + 1
−1,+0,+1 这三种情况分别求出其对应的质因子即可。因为对于操作数组后最终的最大公因数
g
c
d
gcd
gcd ,一定有
∀
a
i
,
g
c
d
∣
a
i
\forall a_i, gcd \mid a_i
∀ai,gcd∣ai,如果
g
c
d
gcd
gcd 是合数,那么也可以质因数分解为一个质数 $d \mid gcd $,同时有
d
×
g
c
d
d
=
g
c
d
,
∀
a
i
,
d
∣
a
i
d \times \frac {gcd} {d} = gcd, \forall a_i,d\mid a_i
d×dgcd=gcd,∀ai,d∣ai
最多可以筛出 K × 11 K\times 11 K×11 个质因子,因为 2 × 3 × ⋯ × 31 ≈ 2 × 1 0 11 < 1 0 12 2\times 3 \times \dots \times 31 \approx 2\times10^{11} < 10^{12} 2×3×⋯×31≈2×1011<1012,每个数最多有 11 11 11 个不同的质因子,其中 K K K 是我们随机的次数
将所有情况的质因子都存入到一个集合中,对于每一个质因子都遍历整个数组,判断满足通过若干次操作后让每个元素都整除当前质因子的条件下的最小操作数,取所有情况的最小值即可
对于通过若干次操作后让每个元素都整除当前质因子的条件下的最小操作数,要么是对当前元素通过若干次 + 1 +1 +1 操作使得满足整除,此操作数是 f a c t − x m o d f a c t fact - x \mod fact fact−xmodfact ,其中 f a c t , x fact, x fact,x 分别是当前枚举的质因子和当前遍历的数组元素。要么是对当前元素通过若干次 − 1 -1 −1 操作使得满足整除,这个情况要求 x > f a c t x > fact x>fact ,否则会使得 x = 0 x = 0 x=0 ,此操作数是 x m o d f a c t x \mod fact xmodfact
时间复杂度 O ( F n + K A ) O(Fn + K\sqrt A) O(Fn+KA),其中 F , K , A F,K,A F,K,A 分别是筛出的质因子的数量,随机的次数,数组 a i a_i ai 的最大值
其中 K A K\sqrt A KA 隐藏了 3 3 3 一个常数,所以可以逆推出 K = 2 × 1 0 8 3 × A ≈ 66 K = \frac {2\times10^8}{3\times \sqrt A} \approx 66 K=3×A2×108≈66,每次随机有 1 2 \frac 1 2 21 的可能性错误,那么随机到正确的数的可能性是 ( 1 − 1 2 ) 66 (1 - \frac 1 2)^{66} (1−21)66 ,几乎不可能全随机错误,其中 2 × 1 0 8 2\times 10^8 2×108 是时限给了两秒,每秒差不多 1 0 8 10^8 108 的计算量
另外 F n = K × 11 × n ≈ 1.5 × 1 0 8 Fn = K \times 11 \times n \approx 1.5 \times 10^8 Fn=K×11×n≈1.5×108,实际上远远到不了这个情况
代码
std::mt19937_64 rnd(std::chrono::steady_clock::now().time_since_epoch().count());
// std::uniform_int_distribution<u64> dist_rand(mod / 2, mod - 1);
void solve() {
int n;
std::cin >> n;
std::vector<i64> a(n);
for (auto &i : a) std::cin >> i;
i64 ans = LINF;
std::set<i64> s;
auto get = [&](i64 x) {
for (i64 i = 2; i * i <= x; i ++) if (x % i == 0) {
s.insert(i);
while (x % i == 0)
x /= i;
}
if (x > 1) s.insert(x);
};
for (int i = 1; i <= 66; i ++) {
std::uniform_int_distribution<u64> dist_rand(0, n - 1);
i64 x = a[dist_rand(rnd)];
for (int j = -1; j <= 1; j ++)
if (x + j > 1)
get(x + j);
}
for (auto fact : s) {
i64 res = 0;
for (auto i : a) {
i64 sub = i % fact;
i64 add = fact - i % fact;
if (i < fact) res += add;
else res += std::min(add, sub);
}
ans = std::min(ans, res);
}
std::cout << ans << '\n';
}