A. Buy a Pen

模拟

代码实现
a = list(map(int, input().split()))
c = input()
a.pop(['Red', 'Green', 'Blue'].index(c))
print(min(a))

B. Right Triangle

一个角为直角等价于这个夹角的两边对应的向量的内积等于 \(0\)

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

using namespace std;

struct Vector {
    int x, y;
    Vector(int x=0, int y=0): x(x), y(y) {}
    Vector operator-(const Vector& o) const {
        return Vector(x-o.x, y-o.y);
    }
    int dot(const Vector& v) {
        return x*v.x + y*v.y;
    }
};

int main() {
    Vector a, b, c;
    cin >> a.x >> a.y;
    cin >> b.x >> b.y;
    cin >> c.x >> c.y;
    
    bool ok = false;
    rep(i, 3) {
        if ((b-a).dot(c-a) == 0) ok = true;
        swap(a, b); swap(b, c);
    }
    
    if (ok) puts("Yes");
    else puts("No");
    
    return 0;
}

C. Sum = 0

显然最小值可以取到 \(\sum L_i\),最大值可以取到 \(\sum R_i\)
可以取到 \(0\) 当且仅当最小值 \(\leqslant 0\) 且最大值 \(\geqslant 0\)

下面考虑如何构造一个合法解

不妨先将每个 \(x_i\) 初始化成 \(L_i\),然后考虑将总的可分配的增量 \(-\sum L_i\) 加给每个 \(x_i\),我们可以让前面的数加到 \(R_i\),如果剩余可分配的增量不够加到 \(R_i\) 的话,就直接将剩余的增量加到当前 \(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;
    cin >> n;
    
    vector<int> l(n), r(n);
    rep(i, n) cin >> l[i] >> r[i];
    
    ll sumL = 0, sumR = 0;
    rep(i, n) sumL += l[i];
    rep(i, n) sumR += r[i];
    
    if (sumL > 0 or sumR < 0) {
        puts("No");
        return 0;
    }
    
    puts("Yes");
    
    vector<int> ans = l;
    ll rem = -sumL;
    rep(i, n) {
        ll canAdd = r[i]-l[i];
        if (canAdd < rem) {
            ans[i] = r[i];
            rem -= canAdd;
        }
        else {
            ans[i] += rem;
            break;
        }
    }
    
    rep(i, n) cout << ans[i] << ' ';
    
    return 0;
}

D. Shortest Path 3

\(\operatorname{Dijkstra}\) 跑最短路的板题

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

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

struct Edge {
    int to, cost;
    Edge(int to, int cost): to(to), cost(cost) {}
};

int main() {
    int n, m;
    cin >> n >> m;
    
    vector<int> a(n);
    rep(i, n) cin >> a[i];
    
    vector<vector<Edge>> g(n);
    rep(i, m) {
        int u, v, b;
        cin >> u >> v >> b;
        --u; --v;
        g[u].emplace_back(v, b+a[v]);
        g[v].emplace_back(u, b+a[u]);
    }
    
    const ll INF = 1e18;
    vector<ll> dist(n, INF);
    priority_queue<P, vector<P>, greater<P>> q;
    dist[0] = a[0]; q.emplace(a[0], 0);
    while (q.size()) {
        auto [d, v] = q.top(); q.pop();
        if (dist[v] != d) continue;
        for (auto [u, w] : g[v]) {
            ll nd = d+w;
            if (dist[u] <= nd) continue;
            dist[u] = nd;
            q.emplace(nd, u);
        }
    }
    
    for (int i = 1; i < n; ++i) cout << dist[i] << ' ';
    
    return 0;
}

E. Count Arithmetic Subsequences

dp[i][j][k] 表示在前 \(i\) 个数中选 \(k\) 个数且最后一个数选 \(a_i\) 且倒数第 \(2\) 个数选 \(a_j\) 的合法方案数
状态数为 \(O(N^3)\),转移次数为 \(O(N)\),这里 \(N\) 比较小所以是可行的

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

using namespace std;
using mint = modint998244353;

mint dp[81][81][81];

int main() {
    int n;
    cin >> n;
    
    vector<int> a(n);
    rep(i, n) cin >> a[i];
    
    rep(i, n) {
        rep(j, i) dp[i][j][2] = 1;
        rep(j, i)rep(k, n) {
            mint now = dp[i][j][k];
            if (now == 0) continue;
            for (int x = i+1; x < n; ++x) {
                if (a[x]-a[i] == a[i]-a[j]) {
                    dp[x][i][k+1] += now;
                }
            }
        }
    }
    
    vector<mint> ans(n+1);
    ans[1] = n;
    for (int k = 2; k <= n; ++k) {
        rep(i, n)rep(j, i) ans[k] += dp[i][j][k];
    }
    
    for (int i = 1; i <= n; ++i) cout << ans[i].val() << ' ';
    
    return 0;
}

也有另一种dp做法
dp[i][j][d] 表示在前 \(i\) 个数中选 \(j\) 个数且最后一个数选 \(a_i\) 使得公差为 \(d\) 的合法方案数
其中公差 \(d\) 可以用 std::unordered_map 来维护
时间复杂度为 \(O(N^3)\)

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

using namespace std;
using mint = modint998244353;

unordered_map<int, mint> dp[81][81];

int main() {
    int n;
    cin >> n;
    
    vector<int> a(n);
    rep(i, n) cin >> a[i];
    
    rep(ni, n)rep(i, ni) {
        int d = a[ni]-a[i];
        dp[ni][2][d] += 1;
        rep(j, n) dp[ni][j+1][d] += dp[i][j][d];
    }
    
    vector<mint> ans(n+1);
    ans[1] = n;
    for (int j = 2; j <= n; ++j) {
        rep(i, n) {
            for (auto [d, x] : dp[i][j]) ans[j] += x;
        }
    }
    
    for (int i = 1; i <= n; ++i) cout << ans[i].val() << ' ';
    
    return 0;
}

F. Perfect Matching on a Tree

注意到,对于每条边可贡献的次数的上界可以取到较小子树的大小
那么,我们只需找到树的重心,然后让每个点穿过重心进行匹配即可
特别地,如果 \(n\) 是奇数的话,那就不匹配重心

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

using namespace std;

int main() {
    int n;
    cin >> n;
    
    vector<vector<int>> to(n);
    rep(i, n-1) {
        int a, b;
        cin >> a >> b;
        --a; --b;
        to[a].push_back(b);
        to[b].push_back(a);
    }
    
    int centroid = 0;
    vector<int> sz(n);
    auto dfs = [&](auto& f, int v, int p=-1) -> int {
        int mx = 0;
        sz[v] = 1;
        for (int u : to[v]) {
            if (u == p) continue;
            sz[v] += f(f, u, v);
            mx = max(mx, sz[u]);
        }
        mx = max(mx, n-sz[v]);
        if (mx*2 <= n) centroid = v; 
        return sz[v];
    };
    dfs(dfs, 0);
    
    vector<int> vs;
    auto dfs2 = [&](auto& f, int v, int p=-1) -> void {
        for (int u : to[v]) {
            if (u == p) continue;
            f(f, u, v);
        }
        vs.push_back(v);
    };
    dfs2(dfs2, centroid);
    if (n%2 == 1) vs.pop_back();
    
    rep(i, n/2) {
        int a = vs[i], b = vs[i+n/2];
        cout << a+1 << ' ' << b+1 << '\n';
    }
    
    return 0;
}

G. Count Substring Query

板题,几乎在任何oj上都有?
对于每个询问就是求对于 \(S\) 的后缀 \(S_1, S_2, \cdots, S_N\),从中找出有多少个后缀满足 \(T\) 是它的前缀。考虑在 \(T\) 后面加一个新的字符 ~,得到 \(T'\),原问题等价于求满足 \(T \leqslant S_i \leqslant T'\)\(i\) 的个数。
关于后缀数组的做法,就是二分出在 \(S\) 的所有后缀中满足 \(S_k \geqslant T\) 的最小的排名 \(k\),快速询问排名对应的后缀的下标就是后缀数组的强项了
就不贴代码了