P8193 [USACO22FEB] 高维前缀和

题意

传送门 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=jiaj。对于第 i i i 个询问,存在满足条件操作的充要条件是 q i ∣ s q_i\vert s qis。令 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 nx,至少需要分裂的次数为 s / q i − x s/q_i-x s/qix,且这样的操作可以满足条件。答案为 n + s / q i − 2 x n + s/q_i -2x n+s/qi2x

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} n1012 ,由于合数存在小于等于 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 max1i106d(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;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值