AtCoder Beginner Contest 362

~Lanly~發表於2024-07-14

A - Buy a Pen (abc362 A)

題目大意

給定紅藍綠三支筆的價格,並不買指定顏色的筆,問買一支筆最少需要多少錢。

解題思路

三種情況逐一判斷,取最小即可。

神奇的程式碼
#include <bits/stdc++.h>
using namespace std;
using LL = long long;

int main(void) {
    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);
    int r, g, b;
    string c;
    cin >> r >> g >> b >> c;
    if (c[0] == 'R') {
        r = 999;
    } else if (c[0] == 'G') {
        g = 999;
    } else {
        b = 999;
    }
    cout << min({r, g, b}) << '\n';

    return 0;
}



B - Right Triangle (abc362 B)

題目大意

給定三點座標,問是否形成直角三角形。

解題思路

列舉直角點,然後向量點積判斷是否成90度即可。

神奇的程式碼
#include <bits/stdc++.h>
using namespace std;
using LL = long long;

int main(void) {
    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);
    array<array<int, 2>, 3> p;
    for (auto& x : p)
        cin >> x[0] >> x[1];
    auto vertical = [](array<int, 2>& a, array<int, 2>& b, array<int, 2>& c) {
        return (a[0] - b[0]) * (a[0] - c[0]) + (a[1] - b[1]) * (a[1] - c[1]) ==
               0;
    };
    if (vertical(p[0], p[1], p[2]) || vertical(p[1], p[2], p[0]) ||
        vertical(p[2], p[0], p[1])) {
        cout << "Yes" << '\n';
    } else {
        cout << "No" << '\n';
    }

    return 0;
}



C - Sum = 0 (abc362 C)

題目大意

給定兩個\(n\)個數的陣列 \(l,r\),構造 \(n\)個數 \(x_i\),滿足:

  • \(l_i \leq x_i \leq r_i\)
  • \(\sum_{i=1}^{n} x_i = 0\)

解題思路

先假定\(x_i = l_i\),此時如果\(\sum_{i=1}^{n} x_i > 0\)則無解。

否則遍歷\(i = 1, 2, 3, ..., n\),對於每個 \(x_i\),依次增大 \(x_i\),直到 \(\sum_{i=1}^{n} x_i = 0\)或者\(x_i = r_i\)。貪心增加即可。

神奇的程式碼
#include <bits/stdc++.h>
using namespace std;
using LL = long long;

int main(void) {
    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);
    int n;
    cin >> n;
    vector<int> l(n), r(n);
    for (int i = 0; i < n; i++) {
        cin >> l[i] >> r[i];
    }
    vector<int> x = l;
    LL sum = accumulate(l.begin(), l.end(), 0ll);
    for (int i = 0; i < n; i++) {
        if (sum < 0) {
            int c = min(-sum, 0ll + r[i] - l[i]);
            x[i] += c;
            sum += c;
        }
    }
    if (sum != 0) {
        cout << "No" << '\n';
    } else {
        cout << "Yes" << '\n';
        for (int i = 0; i < n; i++) {
            cout << x[i] << " \n"[i == n - 1];
        }
    }

    return 0;
}



D - Shortest Path 3 (abc362 D)

題目大意

給定一張無向圖,點有點權\(a_i\),邊有邊權\(w_i\),問\(1\)號點到其他點的最短距離。

距離為沿途的所有點的點權和邊的邊權和。

解題思路

就一個樸素的\(dijkstra\)最短路就解決了,轉移的時候邊\(u \to v\)的代價從\(w_i\)改成 \(w_i + a_v\)

神奇的程式碼
#include <bits/stdc++.h>
using namespace std;
using LL = long long;

int main(void) {
    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);
    int n, m;
    cin >> n >> m;
    vector<int> a(n);
    for (auto& x : a)
        cin >> x;
    vector<vector<array<int, 2>>> edge(n);
    for (int i = 0; i < m; i++) {
        int u, v, w;
        cin >> u >> v >> w;
        --u, --v;
        edge[u].push_back({v, w});
        edge[v].push_back({u, w});
    }
    vector<LL> dis(n, 1e18);
    dis[0] = a[0];
    priority_queue<pair<LL, int>, vector<pair<LL, int>>, greater<pair<LL, int>>>
        team;
    team.push({dis[0], 0});
    while (!team.empty()) {
        auto [d, u] = team.top();
        team.pop();
        if (dis[u] < d)
            continue;
        for (auto& [v, w] : edge[u]) {
            if (dis[v] > dis[u] + w + a[v]) {
                dis[v] = dis[u] + w + a[v];
                team.push({dis[v], v});
            }
        }
    }
    for (int i = 1; i < n; i++)
        cout << dis[i] << " \n"[i == n - 1];

    return 0;
}



E - Count Arithmetic Subsequences (abc362 E)

題目大意

給定\(n\)個數 \(a_i\),對於 \(k = 1, 2, ..., n\),問長度為 \(k\)\(a\)的子序列中,是等差數列的數量。

解題思路

長度為\(1,2\)的等差數列可以直接算出來,因此考慮長度 \(\geq 3\)的情況。

考慮如何統計等差數列,比如考慮列舉公差,統計不同公差下的子序列數量。

雖然公差的範圍\(< 10^9\),但考慮到公差是兩個數的差,其取值實際只有 \(O(n^2)\)個,而 \(n \leq 80\),可以考慮列舉公差。然後統計該公差下各個長度的子序列數量,累計求和即為答案。

列舉公差 \(d\),剩下就是考慮如何統計其子序列的個數。考慮樸素搜尋,即從左到右依次考慮每個數選或不選,選的話要保證構成公差為 \(d\)的等差數列,需要得知上一個選的數是什麼,同時還要保留我已經選了多少個數。

據此容易想到就是一個樸素的 \(dp\)\(dp[i][k]\)表示 考慮前\(i\)個數,且選擇了第 \(i\)個數,且已經選了 \(k\)個數的等差為 \(d\)的子序列數量。 轉移則列舉\(j\),滿足 \(a_i - a_k = d\),則 \(dp[i][k] += dp[j][k - 1]\)。即\(dp[i][k] = \sum_{a_i - a_j = d} dp[j][k - 1]\)

列舉公差 \(O(n^2)\)\(dp\)狀態 \(O(n^2)\),轉移 \(O(n)\),總複雜度是 \(O(n^5)\),是無法透過的,考慮最佳化轉移。

對於一個轉移\(dp[i][k] += dp[j][k - 1]\),其中 \(a_i - a_j = d\),遍歷所有的公差時\(d_i\),其實 只有一個\(d_i = d\)時,會發生這個轉移。 也就是說,固定了公差,我們就可以預處理出狀態轉移的前繼狀態,即\(dp[i]\)可以從什麼 \(dp[j]\) 轉移過來,預處理的複雜度是\(O(n^2)\),隨後在求\(dp\)時,轉移就無需遍歷 \(j \in [1,i)\) ,直接遍歷預處理的轉移即可。

這樣預處理之後,求\(dp\)的複雜度是多少呢?因為每個公差預處理出來的 \(i\)的前繼轉移 \(j\)的數量不同,但注意到一個轉移\(dp[i][k] += dp[j][k - 1]\),其中 \(a_i - a_j = d\),遍歷所有的公差時\(d_i\),其實 只有一個\(d_i = d\)時才發生這個轉移,縱觀所有的這類轉移,其數量有\(O(n^2)\)個,但其因為公差 \(d\)被打散在這 \(O(n^2)\)次求 \(dp\)裡,所以所有公差,每個公差遍歷預處理的轉移前繼狀態,總的複雜度是\(O(n^2)\)個狀態+ \(O(n^2)\)次轉移,總的複雜度還是 \(O(n^4)\)

神奇的程式碼
#include <bits/stdc++.h>
using namespace std;
using LL = long long;

const int mo = 998244353;

int main(void) {
    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);
    int n;
    cin >> n;
    vector<int> a(n);
    for (auto& x : a)
        cin >> x;
    vector<int> diff;
    for (int i = 0; i < n; i++) {
        for (int j = i + 1; j < n; j++) {
            diff.push_back(a[j] - a[i]);
        }
    }
    sort(diff.begin(), diff.end());
    diff.erase(unique(diff.begin(), diff.end()), diff.end());
    vector<int> ans(n + 1, 0);
    ans[1] = n;
    if (n > 1)
        ans[2] = n * (n - 1) / 2;
    for (auto d : diff) {
        vector<vector<int>> tr(n);
        for (int i = 0; i < n; ++i)
            for (int j = 0; j < i; ++j)
                if (a[i] - a[j] == d)
                    tr[i].push_back(j);

        vector<vector<int>> dp(n, vector<int>(n + 1, 0));
        for (int i = 0; i < n; ++i) {
            dp[i][1] = 1;
            for (int j = 1; j <= i; ++j) {
                for (auto& k : tr[i]) {
                    dp[i][j + 1] += dp[k][j];
                    if (dp[i][j + 1] >= mo) {
                        dp[i][j + 1] -= mo;
                    }
                }
            }
        }
        for (int i = 0; i < n; ++i) {
            for (int j = 3; j <= n; ++j) {
                ans[j] += dp[i][j];
                if (ans[j] >= mo) {
                    ans[j] -= mo;
                }
            }
        }
    }
    for (int i = 1; i <= n; ++i) {
        cout << ans[i] << " \n"[i == n];
    }

    return 0;
}



F - Perfect Matching on a Tree (abc362 F)

題目大意

給定一棵樹,倆倆配對,收益是兩個點的最短路邊數\(dis(u,v)\)

構造配對方案,使得收益最大。

解題思路

每次收益是邊數,配對的收益和最大,換個角度考慮貢獻,認為考慮每條邊,其出現在配對最短路的次數。

一條邊將樹拆成兩個連通塊,假設點數分別為\(v_i, n - v_i\),如果配對的兩個點分別在這兩個連通塊裡,這個這條邊對答案就有\(1\)的貢獻 。那一條邊對答案的最大貢獻即為\(min(v_i, n - v_i)\)

所有邊的最大貢獻和,即為收益的上界,考慮這個上界能否取到。很顯然,對於一些\(v_i\) 很大或很小的,\(min(v_i, n - v_i)\)都比較小,這些邊的貢獻上界很容易取到,因此關鍵要考慮 \(v_i = \frac{n}{2}\) 左右的邊。

而這些邊實際是在樹的重心附近,考慮重心,其特點是最大的兒子數不超過 \(\frac{n}{2}\),重心有好幾個兒子,我們的配對目標是,配對的兩個點來自於不同的兒子子樹。這樣每個兒子子樹裡的邊都可以取到上界。

剩下的問題就是如何選擇兒子子樹。事實上,考慮abc359f,其實構造方法是一樣的。

將每個兒子子樹看成一個點,子樹的點樹視為該點的度數,每取兩個子樹的兒子配對,相當於給這兩個子樹點連邊。然後最終每個點的度數滿足要求。

因此構造方法為,每次選擇一個兒子子樹最大的和非最大的,配對。動態維護子樹大小。

如果點數是奇數,則拋棄重心,否則也把重心視為一個子樹。

神奇的程式碼
#include <bits/stdc++.h>
using namespace std;
using LL = long long;

int main(void) {
    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);
    int n;
    cin >> n;
    vector<vector<int>> edge(n);
    for (int i = 0; i < n - 1; i++) {
        int u, v;
        cin >> u >> v;
        u--;
        v--;
        edge[u].push_back(v);
        edge[v].push_back(u);
    }
    vector<int> son(n, 0), weight(n, 0);
    int root = 0;
    auto dfs = [&](auto&& dfs, int u, int fa) -> void {
        son[u] = 1;
        for (auto v : edge[u]) {
            if (v == fa)
                continue;
            dfs(dfs, v, u);
            son[u] += son[v];
            weight[u] = max(weight[u], son[v]);
        }
        weight[u] = max(weight[u], n - son[u]);
        if (weight[u] <= n / 2)
            root = u;
    };
    dfs(dfs, 0, 0);
    vector<vector<int>> child;
    auto dfs2 = [&](auto&& dfs2, int u, int fa, vector<int>& cc) -> void {
        cc.push_back(u);
        for (auto v : edge[u]) {
            if (v == fa)
                continue;
            dfs2(dfs2, v, u, cc);
        }
    };
    for (auto& u : edge[root]) {
        vector<int> cc;
        dfs2(dfs2, u, root, cc);
        child.push_back(cc);
    }
    if (n % 2 == 0)
        child.push_back({root});
    auto cmp = [&](const int a, const int b) -> bool {
        return child[a].size() < child[b].size();
    };
    priority_queue<int, vector<int>, decltype(cmp)> pq(cmp);
    for (int i = 0; i < child.size(); ++i) {
        pq.push(i);
    }
    for (int i = 0; i < n / 2; ++i) {
        auto tu = pq.top();
        pq.pop();
        auto tv = pq.top();
        pq.pop();
        int u = child[tu].back();
        int v = child[tv].back();
        child[tu].pop_back();
        child[tv].pop_back();
        if (child[tu].size() > 0)
            pq.push(tu);
        if (child[tv].size() > 0)
            pq.push(tv);
        cout << u + 1 << " " << v + 1 << '\n';
    }

    return 0;
}



G - Count Substring Query (abc362 G)

題目大意

給定一個字串\(s\),回答 \(q\)個詢問。

每個詢問給定一個字串 \(t\),問字串 \(t\)在字串 \(s\)裡的出現次數。

解題思路

此即為字尾自動機的一個節點的\(|endpos|\)大小,貼個模板即可。

神奇的程式碼
#include <bits/stdc++.h>
using namespace std;
using LL = long long;

class SAM {
    enum { len = 1000005, vary = 26 };

    int trans[len << 1][vary];
    int maxlen[len << 1];
    int link[len << 1];
    int cnt[len << 1];
    int tot;
    int last;

  public:
    SAM() {
        tot = last = 0;
        link[tot] = -1;
        maxlen[tot] = 0;
        ++tot;
    }

    void clear() {
        for (int i = 0; i < tot; ++i) {
            for (int j = 0; j < vary; ++j) {
                trans[i][j] = 0;
            }
            maxlen[i] = link[i] = cnt[i] = 0;
        }
        tot = last = 0;
        link[tot] = -1;
        maxlen[tot] = 0;
        ++tot;
    }

    void insert(int s) {
        int cur = tot++;
        maxlen[cur] = maxlen[last] + 1;
        int p = last;
        for (; p != -1 && !trans[p][s]; p = link[p])
            trans[p][s] = cur;
        if (p == -1)
            link[cur] = 0;
        else {
            int q = trans[p][s];
            if (maxlen[q] == maxlen[p] + 1)
                link[cur] = q;
            else {
                int clone = tot++;
                maxlen[clone] = maxlen[p] + 1;
                link[clone] = link[q];
                for (int i = 0; i < vary; ++i)
                    trans[clone][i] = trans[q][i];
                for (; p != -1 && trans[p][s] == q; p = link[p])
                    trans[p][s] = clone;
                link[q] = link[cur] = clone;
            }
        }
        cnt[cur] = 1;
        last = cur;
    }

    int tong[len];
    int sa[len << 1];

    void build() {
        tong[0] = 0;
        for (int i = 1; i < tot; ++i)
            ++tong[maxlen[i]];
        for (int i = 1; i < len; ++i)
            tong[i] += tong[i - 1];
        for (int i = 1; i < tot; ++i)
            sa[tong[maxlen[i]]--] = i;
        for (int i = tot - 1; i >= 0; --i) {
            int p = sa[i];
            cnt[link[p]] += cnt[p];
        }
    }

    int solve(string& t) {
        int cur = 0;
        for (auto c : t) {
            auto s = c - 'a';
            if (trans[cur][s] == 0)
                return 0;
            cur = trans[cur][s];
        }
        return cnt[cur];
    }
} sam;

int main(void) {
    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);
    string s;
    cin >> s;
    for (auto c : s)
        sam.insert(c - 'a');
    sam.build();
    int q;
    cin >> q;
    while (q--) {
        string t;
        cin >> t;
        int ans = sam.solve(t);
        cout << ans << '\n';
    }

    return 0;
}



相關文章