Codeforces Round #722 (Div. 2)

比赛链接:https://codeforces.com/contest/1529

A. Eshag Loves Big Arrays

题解

反复选取最小值和大于它的数即可。

代码

#include <bits/stdc++.h>
using namespace std;
int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    int t;
    cin >> t;
    while (t--) {
        int n;
        cin >> n;
        vector<int> a(n);
        for (int i = 0; i < n; i++) {
            cin >> a[i];
        }
        cout << n - count(a.begin(), a.end(), *min_element(a.begin(), a.end())) << "\n";
    }
    return 0;
}

B. Sifid and Strange Subsequences

题解

因为要求任意两对都大于等于最大值,所以排序找相邻最小值,然后枚举当前值能否为最大值即可。

代码

#include <bits/stdc++.h>
using namespace std;
int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    int t;
    cin >> t;
    while (t--) {
        int n;
        cin >> n;
        vector<int> a(n);
        for (int i = 0; i < n; i++) {
            cin >> a[i];
        }
        sort(a.begin(), a.end());
        int mi = INT_MAX, ans = 0;
        for (int i = 0; i < n; i++) {
            if (i > 0) {
                mi = min(mi, a[i] - a[i - 1]);
            }
            if (mi >= a[i]) {
                ans = i + 1;
            }
        }
        cout << ans << "\n";
    }
    return 0;
}

C. Parsa's Humongous Tree

题解

每个结点取 \(l_i\)\(r_i\) 收益最大,树形 \(dp\)

证明

假设一个结点取 \((l_i, r_i)\) 间的某值,设与它相邻的结点中小于此值的有 \(p\) 个,大于此值的有 \(q\) 个,根据 \(p, q\) 的大小关系可以分为以下三种情况:

  • \(p < q\) ,此时取值向 \(l_i\) 靠近收益一定增加
  • \(p > q\) ,此时取值向 \(r_i\) 靠近收益一定增加
  • \(p = q\) ,此时取值向 \(l_i\)\(r_i\) 靠近收益一定不会减少

所以,每个结点只取 \(l_i\)\(r_i\) 即可。

代码

#include <bits/stdc++.h>
using namespace std;
int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    int t;
    cin >> t;
    while (t--) {
        int n;
        cin >> n;
        vector<int> l(n), r(n);
        for (int i = 0; i < n; i++) {
            cin >> l[i] >> r[i];
        }
        vector<vector<int>> G(n);
        for (int i = 0; i < n - 1; i++) {
            int u, v;
            cin >> u >> v;
            --u, --v;
            G[u].push_back(v);
            G[v].push_back(u);
        }
        vector<vector<long long>> dp(n, vector<long long> (2));
        function<void(int, int)> dfs = [&](int u, int p) {
            for (auto v : G[u]) {
                if (v == p) {
                    continue;
                }
                dfs(v, u);
                dp[u][0] += max(dp[v][0] + abs(l[u] - l[v]), dp[v][1] + abs(l[u] - r[v]));
                dp[u][1] += max(dp[v][0] + abs(r[u] - l[v]), dp[v][1] + abs(r[u] - r[v]));
            }
        };
        dfs(0, -1);
        cout << max(dp[0][0], dp[0][1]) << "\n";
    }
    return 0;
}

D. Kavi on Pairing Duty

题解

假设点 \(1\)\(x\) 配对,根据 \(x\)\(n\) 的大小关系可以分为以下两种情况:

\(x > n\)

  • 此时 \(x + 1\) 由于不被 \([1,x]\) 包含,只能与 \(2\) 配对。

  • 同理, \([x, 2n]\) 间的点只能依次与 \([1, 1 + 2n - x]\) 配对。

  • 此时在 \((1 + 2n - x, x)\) 中还有 \(2(x - n - 1)\) 个点,即余下的点总为 \(2\) 的倍数,所以可以设 \(dp_i\)\(2i\) 个点时的方案数。

  • \(x\) 取大于 \(n\)\(n+1, n+2,\dots,2n\) 时,余下的点依次为 \(2 \times (0,1,\dots,n-1)\) 个,由于这些点被所有区间包含,所以只需考虑它们自身即可。即,当 \(x > n\) 时,总的方案数之和为 \(\sum \limits _{i = 0}^{n - 1} dp_i\)

\(x \le n\)

  • 此时 \(x + 1\) 可以与 \(2\)\(2x\) 配对。若 \(x + 1\)\(2x\) 配对,由于 \(2\) 不被 \([x + 1, 2x]\) 包含所以必须与 \(x + 1\) 配对,矛盾,所以 \(x + 1\) 仍只能与 \(2\) 配对。

  • 同理, \([x, 2(x - 1)]\) 间的点只能依次与 \([1, x - 1]\) 配对。

  • 此时 \(2n\) 个点被分割成了一个个长为 \(2(x - 1)\) 的小整体,所以 \(x - 1\) 需要整除 \(n\) ,又由于 \(x - 1 \le n - 1\) ,所以共有 \(n\) 的真因子个数种分法。即 \(x \le n\) 的情况共有 \(div_n\) 种分法。

综上,推得 \(dp_n = div_n + \sum \limits _{i = 0}^{n - 1} dp_i\)

代码

#include <bits/stdc++.h>
using namespace std;
int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    constexpr int N = 1e6 + 10;
    vector<int> div(N);
    for (int i = 1; i < N; i++) {
        for (int j = i + i; j < N; j += i) {
            ++div[j];
        }
    }
    constexpr int MOD = 998244353;
    vector<int> dp(N);
    int sum = dp[0] = 1;
    for (int i = 1; i < N; i++) {
        dp[i] = (div[i] + sum) % MOD;
        sum = (sum + dp[i]) % MOD;
    }
    int n;
    cin >> n;
    cout << dp[n] << "\n";
    return 0;
}

E. Trees of Tranquillity

题解

由于构造图为完全图,所以其中的点在树一中两两存在父子关系,即在树一中为一条链,所以可以用 \(dfs\) 回溯遍历树一中的链。

为了快速判断两点在树二上是否存在父子关系,可以用 \(dfs\) 序打时间戳,若 \(v\)\(u\) 的子代,则一定有 \(in_u < in_v < out_u\) ,同时,由于贪心策略,若两点在树二中存在父子关系,取子结点更优。

代码

#include <bits/stdc++.h>
using namespace std;
int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    int t;
    cin >> t;
    while (t--) {
        int n;
        cin >> n;
        vector<vector<int>> G1(n);
        for (int v = 1; v < n; v++) {
            int u;
            cin >> u;
            --u;
            G1[u].push_back(v);
        }
        vector<vector<int>> G2(n);
        for (int v = 1; v < n; v++) {
            int u;
            cin >> u;
            --u;
            G2[u].push_back(v);
        }
        vector<int> in(n), out(n), id(n);
        int timeStamp = 0;
        function<void(int)> dfsG2 = [&](int u) {
            in[u] = timeStamp++;
            id[in[u]] = u;
            for (auto v : G2[u]) {
                dfsG2(v);
            }
            out[u] = timeStamp;
        };
        dfsG2(0);
        int ans = 0;
        set<int> st;
        function<void(int)> dfsG1 = [&](int u) {
            bool add = false;
            int del = -1;
            auto it = st.upper_bound(in[u]);
            if (it != st.end() and *it < out[u]) { //若 set 中已有子结点,由贪心策略,取子结点更优
                
            } else {
                if (it != st.begin()) {
                    --it;
                    if (out[id[*it]] > in[u]) { //同上, erase 掉父结点
                        del = *it;
                        st.erase(it);
                    }
                }
                st.insert(in[u]);
                add = true;
            }
            ans = max(ans, (int)st.size());
            for (auto v : G1[u]) {
                dfsG1(v);
            }
            if (add) {
                st.erase(in[u]);
                if (del != -1) {
                    st.insert(del);
                }
            }
        };
        dfsG1(0);
        cout << ans << "\n";
    }
    return 0;
}

F. It's a bird! No, it's a plane! No, it's AaParsa!

题解

本质上还是最短路问题,只不过多出了每个点等待的时间。

由于点的个数 \(n\) 远少于边数 \(m\) ,所以采用 \(O_{(n^2)}\)\(Dijkstra\)

代码

#include <bits/stdc++.h>
using namespace std;
int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    int n, m;
    cin >> n >> m;
    vector<vector<pair<int, int>>> G(n);
    for (int i = 0; i < m; i++) {
        int u, v, w;
        cin >> u >> v >> w;
        G[u].emplace_back(v, w);
    }
    const int INF = 1e9 + n;
    for (int s = 0; s < n; s++) {
        vector<int> dis(n, INF);
        vector<bool> vis(n);
        for (auto [v, w] : G[s]) {
            dis[v] = w;
        }
        for (int loop = 0; loop < n; loop++) {
            int u = -1;
            for (int i = 0; i < n; i++) {
                if (vis[i]) {
                    continue;
                }
                if (u == -1 or dis[i] < dis[u]) {
                    u = i;
                }
            }
            vis[u] = true;
            for (int wait = 0; wait < n; wait++) {
                dis[(u + wait) % n] = min(dis[(u + wait) % n], dis[u] + wait);
            } //其实只考虑每个点等 1 秒的情况即可,和等 n 秒的情况是等价的
            for (auto [v, w] : G[u]) {
                dis[(v + dis[u]) % n] = min(dis[(v + dis[u]) % n], w + dis[u]);
            }
        }
        dis[s] = 0;
        for (int i = 0; i < n; i++) {
            cout << dis[i] << " \n"[i == n - 1];
        }
    }
    return 0;
}
posted @ 2021-06-02 19:16  Kanoon  阅读(60)  评论(0编辑  收藏  举报