C. Cantrip

每个 \(k\) 的答案就是第 \(k\)x 的位置(若不存在则为 \(n\)

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

using namespace std;

int main() {
    int n;
    string s;
    cin >> n >> s;
    
    vector<int> ans;
    rep(i, n) {
        if (s[i] == 'x') ans.push_back(i+1);
    }
    while (ans.size() < n) ans.push_back(n);
    
    rep(i, n) cout << ans[i] << '\n';
    
    return 0;
}

D. The Big Two

把每次决赛对 \((A_m ,B_m)\) 看成无向边,题目等价于统计大小为 \(2\) 的顶点覆盖对数(每条边至少有一个端点在这两个顶点中)。

任取第一条边 \((s,t)\),任一合法对必包含 \(s\) 或包含 \(t\),因此只需枚举
\(v∈{s,t}\)

固定 \(v\) 后,删去所有与 \(v\) 相连的边,剩下的每条边都必须被另一个顶点 \(u\) 覆盖,故 \(u\) 必须出现在所有剩余边中(即在这些边中出现次数等于剩余边数)。统计满足条件的 \(u\) 即得与 \(v\) 配对的数量。

\(s,t\) 分别计数并合并(注意避免重复计数 \((s,t)\) 本身)。复杂度 \(\mathcal{O}(m+n)\)

代码实现
#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<P> es;
    rep(i, m) {
        int a, b;
        cin >> a >> b;
        --a; --b;
        es.emplace_back(a, b);
    }
    
    int ans = 0;
    auto [s, t] = es[0];
    for (int v : {s, t}) {
        int num = 0;
        vector<int> cnt(n);
        cnt[v] = -1;
        if (v == t) cnt[s] = -1;
        for (auto [a, b] : es) {
            if (a == v or b == v) continue;
            num++;
            cnt[a]++; cnt[b]++;
        }
        rep(i, n) {
            if (cnt[i] == num) ans++;
        }
    }
    
    cout << ans << '\n';
    
    return 0;
}

E. Pro Exam Eligibility

二分答案
假设最大胜率为 \(x\)

\(x \leqslant \frac{\text{和}}{\text{长度}} \Leftrightarrow \text{和}-\text{长度} \times x \geqslant 0\)

这里的和指的是区间里 o 的个数,可以用前缀和来维护(\(s_i\)x 时权值为 \(1\),否则权值为 \(0\)

不妨记这个前缀和为 \(a\),和就是 \(a_r - a_{l-1}\),为了让和最大,可以取最小的 \(a_{l-1}\)

代码实现
#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, k;
    string s;
    cin >> n >> k >> s;
    
    auto judge = [&](double x) {
        vector<double> a(n+1);
        rep(i, n) {
            double w = -x;
            if (s[i] == 'o') w += 1;
            a[i+1] = a[i]+w;
        }
        
        int p = 0, o = 0;
        double minA = 1e18;
        rep(r, n) {
            if (s[r] == 'o') o++;
            while (o >= k) {
                minA = min(minA, a[p]);
                if (s[p] == 'o') o--;
                p++;
            }
            // a[r+1]-a[l] (0 <= l < p)
            if (a[r+1]-minA >= 0) return true; 
        }
        return false;
    };
    
    double ac = 0, wa = 1;
    rep(ti, 25) {
        double wj = (ac+wa)/2;
        if (judge(wj)) ac = wj; else wa = wj;
    }
    
    printf("%.10f\n", ac);
    
    return 0;
}

F. GCD Maximum Spanning Tree

最大生成树可以用 \(\text{Kruskal}\):按边权从大到小加入不产生环的边并累加权值。

注意若边权为 \(w\),那么只有数值都被 \(w\) 整除的顶点对才可能有 \(\gcd(A_i,A_j) \geqslant w\)。因此枚举 \(w\) 从大到小,考虑所有值 \(i=w,2w,3w, \cdots\) 中存在于输入的数,把这些值用并查集合并:遇到两个不在同一分量的存在值就合并并加上权重 \(w\)(相当于在这组中选取若干条权为 \(w\) 的边连接分量)。

这样等价于对所有边按权降序的 \(\text{Kruskal}\),最终得到最大生成树和。

代码实现
#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;

int main() {
    int n;
    cin >> n;
    
    int m = 1e6 ;
    vector<bool> exist(m+1);
    rep(i, n) {
        int a;
        cin >> a;
        exist[a] = true;
    }
    
    ll ans = 0;
    dsu uf(m+1);
    for (int w = m; w >= 1; --w) {
        int p = -1;
        for (int i = w; i <= m; i += w) {
            if (exist[i]) {
                if (p != -1 and !uf.same(p, i)) {
                    uf.merge(p, i);
                    ans += w;
                }
                p = i;
            }
        }
    } 
    
    cout << ans << '\n';
    
    return 0;
}