题意
传送门 P8193 [USACO22FEB] Sleeping in Class P
题解
令 s = ∑ a i s = \sum a_i s=∑ai, p r e s u m i = ∑ j ≤ i a j presum_i = \sum_{j\leq i}a_j presumi=∑j≤iaj。对于第 i i i 个询问,存在满足条件操作的充要条件是 q i ∣ s q_i\vert s qi∣s。令 x = ∑ [ p r e s u m i m o d q i = 0 ] x = \sum [presum_i \mod q_i = 0] x=∑[presumimodqi=0]。 至少需要合并的次数为 n − x n-x n−x,至少需要分裂的次数为 s / q i − x s/q_i-x s/qi−x,且这样的操作可以满足条件。答案为 n + s / q i − 2 x n + s/q_i -2x n+s/qi−2x。
q i q_i qi 是 s s s 的因数,则对有所有前缀和,只需考虑它们与 s s s 的公因子。将所有数字按照算数基本定理进行分解,则问题转化为求 q i q_i qi 对应的高维前缀和。高维前缀和可参考 Some SOS DP Insights。
大数分解可以使用 Pollard-Rho 算法。另一种做法是只筛 [ 1 , 1 0 6 ] [1,10^6] [1,106] 的质因子,若最后得到的数 n ≤ 1 0 12 n\leq 10^{12} n≤1012 ,由于合数存在小于等于 n \sqrt{n} n 的因子,故 n n n 是质数,质因数分解完毕,直接算高维前缀和即可。若 n > 1 0 12 n>10^{12} n>1012,则存在两个大于 1 0 12 10^{12} 1012 的质数,则总的质因子数上界为 m a x 1 ≤ i ≤ 1 0 6 d ( i ) × 2 2 max_{1\leq i\leq 10^6}d(i) \times 2^2 max1≤i≤106d(i)×22,其中 d ( i ) d(i) d(i) 代表 i i i 的因数数量,实际上数量较小,可以直接暴力算 x x x,记忆化优化即可。
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
ll gcd(ll a, ll b) { return b == 0 ? a : gcd(b, a % b); }
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int n;
cin >> n;
vector<ll> a(n);
for (int i = 0; i < n; ++i) {
cin >> a[i];
}
for (int i = 0; i + 1 < n; ++i) {
a[i + 1] += a[i];
}
ll s = a[n - 1];
for (int i = 0; i < n; ++i) {
a[i] = gcd(a[i], s);
}
map<ll, int> prime;
bool bf = 0;
{
ll x = s;
for (int i = 2; i <= 1e6; ++i) {
while (x % i == 0) {
prime[i] += 1;
x /= i;
}
}
if (x > 1) {
if (x > 1e12) {
bf = 1;
} else {
prime[x] += 1;
}
}
}
vector<int> dim;
for (auto [x, c] : prime) {
dim.push_back(c + 1);
}
int m = dim.size();
vector<int> bs(m + 1);
bs[0] = 1;
for (int i = 0; i < m; ++i) {
bs[i + 1] = bs[i] * dim[i];
}
auto get = [&](ll x) {
vector<int> c;
for (auto [p, _] : prime) {
int t = 0;
while (x % p == 0) {
++t;
x /= p;
}
c.push_back(t);
}
int res = 0;
for (int i = 0; i < m; ++i) {
res += c[i] * bs[i];
}
return res;
};
vector<int> sum(bs[m]);
for (auto x : a) {
sum[get(x)] += 1;
}
for (int i = 0; i < m; ++i) {
for (int j = bs[m] - 1; j >= 0; --j) {
if (j / bs[i] % dim[i] == dim[i] - 1) continue;
sum[j] += sum[j + bs[i]];
}
}
int q;
cin >> q;
map<ll,ll> mem;
while (q--) {
ll b;
cin >> b;
if (s % b > 0) {
cout << "-1\n";
continue;
}
if (bf) {
if (mem.count(b) == 0) {
int x = 0;
for (int i = 0; i < n; ++i) {
x += a[i] % b == 0;
}
mem[b] = s / b + n - 2 * x;
}
cout << mem[b] << '\n';
} else {
ll res = s / b + n - sum[get(b)] * 2;
cout << res << '\n';
}
}
return 0;
}