HDU5514 Frogs

发布时间 2023-08-27 03:45:56作者: 空白菌

题目链接

题目

Problem Description

There are m stones lying on a circle, and n frogs are jumping over them.
The stones are numbered from 0 to m−1 and the frogs are numbered from 1 to n. The i-th frog can jump over exactly ai stones in a single step, which means from stone j mod m to stone (j+ai) mod m (since all stones lie on a circle).

All frogs start their jump at stone 0, then each of them can jump as many steps as he wants. A frog will occupy a stone when he reach it, and he will keep jumping to occupy as much stones as possible. A stone is still considered ``occupied" after a frog jumped away.
They would like to know which stones can be occupied by at least one of them. Since there may be too many stones, the frogs only want to know the sum of those stones' identifiers.

Input

There are multiple test cases (no more than 20), and the first line contains an integer t,
meaning the total number of test cases.

For each test case, the first line contains two positive integer n and m - the number of frogs and stones respectively (1≤n≤104, 1≤m≤109).

The second line contains n integers a1,a2,⋯,an, where ai denotes step length of the i-th frog (1≤ai≤10^9).

Output

For each test case, you should print first the identifier of the test case and then the sum of all occupied stones' identifiers.

Sample Input

3
2 12
9 10
3 60
22 33 66
9 96
81 40 48 32 64 16 96 42 72

Sample Output

Case #1: 42
Case #2: 1170
Case #3: 1872

Source

2015ACM/ICPC亚洲区沈阳站-重现赛(感谢东北大学)

题解

知识点:容斥原理,因数集合,GCD与LCM。

对于每个 \(a_i\)\(d = \gcd(a_i,m)\) 为这个 \(a_i\) 在模 \(m\) 意义下的能跳到的最小正整数,于是我们可以得到 \(0,d,2d,\cdots\) 这些位置都是能跳到的,因此 \(a_i\) 能跳到的数的和是 \(\displaystyle \sum_{i=0}^{\lfloor \frac{m}{d} \rfloor} id\) 可以用等差数列求和 \(O(1)\) 得到。

因为 \(\gcd\) 一定是 \(m\) 的因数,所以我们只需要考虑其因数的贡献,需要先处理 \(m\) 的所有因数。

每个位置可能被重复跳到,但我们只需要记录一次即可。因此,若我们考虑了 \(d\) ,那么 \(d\) 的倍数因数都不需要考虑了。同时,比如 \(m\) 存在 \(2,3,6\) 三个因数,当 \(2,3\) 被考虑了,那么 \(6\) 就算了两次,此时 \(6\) 不仅不能考虑,还要减掉一次贡献。

我们设 \(cnt_d\) 表示 \(d\) 这个数字需要考虑几次,将出现的 \(d\) 及其倍数因数初始化为 \(1\) ,表示需要考虑 \(1\) 次。之后,若 \(cnt_d = 0\) ,表示考虑过了不再考虑;若 \(cnt_d<0\) ,表示多考虑了 \(|cnt_d|\) 次要减掉这几次的贡献,过程中容斥维护 \(cnt_d\)

我们从小到大遍历 \(m\) 的因数 \(d\) ,每次加上 \(cnt_d \cdot w\)\(w\) 表示 \(d\) 能跳到的数的和。然后,我们将 \(d\) 的倍数因数 \(d'\)\(cnt_{d'}\)\(1\) ,表示这些数在将来考虑的次数减 \(1\)

注意,因为因数个数的上限 \(O(\sqrt m)\) 很松,所以实际上不会超时。

时间复杂度 \(O(n \sqrt m + m)\)

空间复杂度 \(O(n + \sqrt m)\)

代码

#include <bits/stdc++.h>
using namespace std;
using ll = long long;

int a[10007];
void get_factor(int n, vector<int> &factor) {
    for (int i = 1;i * i <= n;i++) {
        if (!(n % i)) {
            factor.push_back(i);
            if (i != n / i) factor.push_back(n / i);
        }
    }
}

bool solve() {
    int n, m;
    cin >> n >> m;
    for (int i = 1;i <= n;i++) cin >> a[i];
    vector<int> factor;
    get_factor(m, factor);
    sort(factor.begin(), factor.end());
    factor.pop_back();
    set<int> st;
    vector<int> cnt(factor.size());
    for (int i = 1;i <= n;i++) {
        int d = gcd(a[i], m);
        if (st.count(d)) continue;
        st.insert(d);
        for (int j = 0;j < factor.size();j++) if (!(factor[j] % d)) cnt[j] = 1;
    }
    ll ans = 0;
    for (int i = 0;i < cnt.size();i++) {
        if (!cnt[i]) continue;
        int num = m / factor[i];
        ans += 1LL * (0 + (num - 1) * factor[i]) * num / 2 * cnt[i];
        for (int j = i + 1;j < cnt.size();j++) if (!(factor[j] % factor[i])) cnt[j] -= cnt[i];
    }
    cout << ans << '\n';
    return true;
}

int main() {
    std::ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
    int t = 1;
    cin >> t;
    for (int i = 1;i <= t;i++) {
        cout << "Case #" << i << ": ";
        if (!solve()) cout << -1 << '\n';
    }
    return 0;
}