C. Odd One Subsequence

开个桶来统计每种数的个数,答案就是 \(\sum \binom{cnt[x]}{2} \times (n-cnt[x])\)

代码实现
#include <bits/stdc++.h>
#define rep(i, n) for (int i = 0; i < (n); ++i)

using namespace std;
using ll = long long;

ll c2(ll n) {
    return n*(n-1)/2;
}

int main() {
    int n;
    cin >> n;
    
    vector<int> a(n);
    rep(i, n) cin >> a[i], a[i]--;
    
    vector<int> cnt(n);
    rep(i, n) cnt[a[i]]++;
    
    ll ans = 0;
    rep(i, n) {
        ans += c2(cnt[i])*(n-cnt[i]);
    }
    
    cout << ans << '\n';
    
    return 0;
}

D. On AtCoder Conference

先把圆环上有人的点聚合并按位置升序展开成两圈线性序列,然后用双指针在这个线性序列上对每个“第一个遇到的人点”计算其覆盖的起点段长度乘以“从该点起遇到 \(\geqslant C\) 人的总数”,逐段累加得到 \(\sum X_i\)

代码实现
#include <bits/stdc++.h>
#define rep(i, n) for (int i = 0; i < (n); ++i)

using namespace std;
using ll = long long;

int main() {
    int n, c; 
    ll m;
    cin >> n >> m >> c;
    
    vector<ll> a(n);
    rep(i, n) cin >> a[i];
    
    map<ll, int> cnt;
    rep(i, n) cnt[a[i]]++;
    
    vector<pair<ll, int>> d;
    for (auto p : cnt) d.emplace_back(p);
    for (auto p : cnt) d.emplace_back(p.first+m, p.second);
    int dn = cnt.size();
    
    ll px = d[dn-1].first-m;
    int r = 0, now = 0;
    ll ans = 0;
    rep(l, dn) {
        while (now < c) {
            now += d[r].second;
            r++;
        }
        auto [x, num] = d[l];
        ans += (x-px)*now;
        px = x;
        now -= num;
    }
    
    cout << ans << '\n';
    
    return 0;
}

E. Hit and Away

以所有安全点为起点跑多源bfs,求出到其他点的 \(\mathrm{top}2\) 距离及其对应的源点

代码实现
#include <bits/stdc++.h>
#define rep(i, n) for (int i = 0; i < (n); ++i)

using namespace std;
using P = pair<int, int>;

int main() {
    int n, m;
    cin >> n >> m;
    
    vector<vector<int>> to(n);
    rep(i, m) {
        int a, b;
        cin >> a >> b;
        --a; --b;
        to[a].push_back(b);
        to[b].push_back(a);
    }
    
    string s;
    cin >> s;
    
    vector<vector<P>> dist(n);
    queue<tuple<int, int, int>> q;
    
    auto push = [&](int v, int sv, int d) {
        if (dist[v].size() >= 2) return;
        for (auto p : dist[v]) if (p.second == sv) return;
        dist[v].emplace_back(d, sv);
        q.emplace(v, sv, d);
    };
    rep(v, n) if (s[v] == 'S') push(v, v, 0);
    
    while (q.size()) {
        auto [v, sv, d] = q.front(); q.pop();
        for (int u : to[v]) push(u, sv, d+1);
    }
    
    rep(v, n) if (s[v] == 'D') {
        int ans = dist[v][0].first + dist[v][1].first;
        cout << ans << '\n';
    }
    
    return 0;
}

F. Shortest Path Query

热带半环+线段树
将每一列看成是一维序列上的一个点,用线段树维护三维矩阵的 \((\min, +)\) 运算,只需求出从左上角走到右下角在纵向上的最小步数,然后加上横向上固定的 \(n-1\) 步,就是最后的答案。

代码实现
#include <bits/stdc++.h>
#include <atcoder/all>
using namespace atcoder;
#define rep(i, n) for (int i = 0; i < (n); ++i)

using namespace std;

const int INF = 1001001001;

using A = array<int, 3>;
using S = array<A, 3>;
const S inf = { A({INF, INF, INF}), A({INF, INF, INF}), A({INF, INF, INF}) };
S op(S a, S b) {
    S c = inf;
    rep(i, 3)rep(j, 3)rep(k, 3) c[i][k] = min(c[i][k], a[i][j]+b[j][k]);
    return c;
}
S e() {
    return S({A({0, INF, INF}), A({INF, 0, INF}), A({INF, INF, 0})});
}
S toS(const string& s) {
    S a = inf;
    rep(i, 3)rep(j, 3) a[i][j] = abs(i-j);
    rep(k, 3) if (s[k] == '#') {
        rep(i, k+1) for (int j = k; j < 3; ++j) {
            a[i][j] = a[j][i] = INF;
        }
    }
    return a;
}

int main() {
    int n;
    cin >> n;
    
    vector<string> s(n, "...");
    {
        vector<string> S(3);
        rep(i, 3) cin >> S[i];
        rep(i, 3)rep(j, n) s[j][i] = S[i][j];
    }
    
    segtree<S, op, e> t(n);
    rep(i, n) t.set(i, toS(s[i]));
    
    int q;
    cin >> q;
    
    rep(qi, q) {
        int r, c;
        cin >> r >> c;
        --r; --c;
        s[c][r] ^= '.'^'#';
        t.set(c, toS(s[c]));
        
        S a = t.all_prod();
        int ans = a[0][2] + n-1;
        if (ans >= INF) ans = -1;
        cout << ans << '\n';
    }
    
    return 0;
}

G. Sum of Pow of Mod of Linear

考虑把 \(\{ (Ak+B) \bmod M \big| k = 0, 1, 2, \cdots, N-1\}\)\(N\) 个数,划分成若干个等差数列。

对于一个首项为 \(d_0\),公差为 \(d_1\),长为 \(n\) 的等差数列,其对答案的贡献为 \(\sum\limits_{i=0}^{n-1} X^{d_1i + d_0}\) 。这个是可以用类似快速幂的方法在 \(O(\log n)\) 时间内计算的。

因此,现在的问题是如何把上面那个东西分解成尽可能少的等差数列。事实上,有一种经典方法是这样的:

  • 找一个参数 \(d\),然后计算 \(h = Ad \bmod M\)
  • \(0, 1, \cdots, N-1\) 这些下标先按 \(\bmod M\) 分成 \(d\) 组,一组中有 \(O(N/d)\) 个元素。
  • 注意到每一组内,相邻像个元素的差是 \((A(i+d)+B) - (Ai+B) = Ad = h\) 。那么,假如当前元素是 \(x\),接下来的元素就是 \(x+h, x+2h, x+3h, \cdots\) 如果 \(0 \leqslant x < h\),那要超过 \(M\) 至少要经过 \(O(\frac{M}{h})\) 个元素。这个过程中经过的元素都不超过 \(M\),所以它们 \(\bmod M\) 之后不变,可以被直接划进一个等差数列。
  • 于是,可以发现一组内可以每 \(O(M/h)\) 个元素划成一个等差数列,因此,一组可以被分成 \(O(\frac{N/d}{M/h}) = O(\frac{hN}{dM})\) 个等差数列。
  • 共有 \(d\) 组,所以公共的等差数列数为 \(O(\frac{hN}{M})\)

为了让这个总等差数列数很小,我们需要找到一个足够小的 \(h\)

考虑设置一个阈值 \(D=\sqrt{10^9}\),然后观察 \(A \bmod M, 2A \bmod M, 3A \bmod M, \cdots, DA \bmod M\)\(D\) 个数。根据抽屉原理,这 \(D\) 个数中最接近的两个数不超过 \(O(\frac{M}{D})\)。假设它们分别是 \(iA \bmod M\)\(jA \bmod M\),那么 \((j-i)A \bmod M\) 就是一个绝对值不超过 \(\frac{M}{D}\) 的数了,可以令 \(d = j-i\)\(h = (j-i)A \bmod M\)

此时,总等差数列数为 \(O(\frac{hN}{M}) = O(\frac{N}{D}) = O(D)\)

于是,使用上述方法划分出不超过 \(O(D)\) 个等差数列,再对每个等差数列以 \(O(\log M)\) 的代价计算贡献即可。

代码实现
#include <bits/stdc++.h>
#include <atcoder/all>
using namespace atcoder;
#define rep(i, n) for (int i = 0; i < (n); ++i)

using namespace std;
using ll = long long;
using mint = modint;

// sum x^{a*i+b}
mint f(ll a, ll b, mint x, int n) {
    mint res = 0, sum = 1, pw = x.pow(a);
    while (n) {
        if (n&1) res = res*pw + sum;
        sum += sum*pw; pw *= pw;
        n >>= 1;
    }
    return res*x.pow(b);
}

mint full(ll a, ll b, mint x, ll m) {
    ll g = gcd(a, m);
    return f(g, b%g, x, m/g)*g;
}

mint solve() {
    int n, m, x, mod; ll a, b;
    cin >> n >> m >> a >> b >> x >> mod;
    mint::set_mod(mod);
    
    mint ans;
    if (a == 0) return mint(x).pow(b)*n;
    if (n >= m) {
        ans += full(a, b, x, m)*(n/m);
        n %= m;
    }
    if (n <= 1) {
        rep(i, n) ans += mint(x).pow((a*i+b)%m);
        return ans;
    }
    
    const int D = sqrt(1e9);
    
    int dy = m, dx = 1; bool rev = false;
    {
        vector<pair<int, int>> ps;
        rep(i, min(D, n)) {
            ps.emplace_back((a*i+b)%m, i);
        }
        sort(ps.begin(), ps.end());
        
        rep(i, ps.size()-1) {
            int ey = ps[i+1].first - ps[i].first;
            int ex = ps[i+1].second - ps[i].second;
            if (ey < dy) {
                dy = ey;
                dx = abs(ex);
                rev = (ex < 0);
            }
        }
    }
    
    if (rev) {
        b = (a*(n-1)+b)%m;
        a = (m-a)%m;
    }
    
    rep(si, dx) {
        for (int i = si; i < n;) {
            ll c = (a*i+b)%m;
            int w = dy ? (m-1-c)/dy+1 : n;
            w = min(w, (n-1-i)/dx+1);
            ans += f(dy, c, x, w);
            i += dx*w;
        }
    }
    
    return ans;
}

int main() {
    int t;
    cin >> t;
    
    while (t--) cout << solve().val() << '\n';
    
    return 0;
}