2024 暑假友谊赛 1

2024 暑假友谊赛 1

A - 😜

AtCoder - abc204_d

题意

现有 N 道菜需要连续使用烤箱 \(T_i\) 分钟,你有两个烤箱,问你烹饪 N 道菜所需最短时间。

思路

可以猜想一定是 \(\frac{\sum_{i=1}^nT_i}{2}\) 附近,贪心不会,考虑 dp。

用背包 dp 将所有数的拼凑方案表示出来,因为 \(1≤N≤100,1≤T_i≤10^3\),所以最大值不会超过 1e5,然后遍历可以拼凑的方案,更新最小值即可。

代码

#include<bits/stdc++.h>

using namespace std;

using i64 = long long;

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);

    int n;
    cin >> n;

    int sum = 0;
    vector<int> T(n + 1);
    for (int i = 1; i <= n; i ++) {
        cin >> T[i];
        sum += T[i];
    }

    int ans = 1 << 30;
    const int N = 1e5;
    vector<int> dp(N + 10);
    dp[0] = 1;
    for (int i = 1; i <= n; i ++) {
        for (int j = N; j >= T[i]; j--) {
            dp[j] |= dp[j - T[i]];
        }
    }

    for (int i = 0; i <= N; i++) {
        if (dp[i]) {
            ans = min(ans, max(i, sum - i));
        }
    }

    cout << ans << '\n';


    return 0;
}

B - 😭

AtCoder - arc092_a

题意

给你 n 个点,当且仅当 \(x_i<x_j\) 并且 \(y_i<y_j\) 时,\((i,j)\) 可以组成一对,一个点不能被多个点对公用,问最多可以凑成多少对。

思路

前置知识:二分图最大匹配、匈牙利算法。

把凑成一对看成由 i 向 j 连一条边,这道题就是典型的二分图最大匹配模板题,由于直接用的板子,所以没啥细节了。

代码

#include<bits/stdc++.h>

using namespace std;

using i64 = long long;

struct augment_path {
    vector<vector<int> > g;
    vector<int> pa;  // 匹配
    vector<int> pb;
    vector<int> vis;  // 访问
    int n, m;         // 两个点集中的顶点数量
    int dfn;          // 时间戳记
    int res;          // 匹配数

    augment_path(int _n, int _m) : n(_n), m(_m) {
        assert(0 <= n && 0 <= m);
        pa = vector<int>(n, -1);
        pb = vector<int>(m, -1);
        vis = vector<int>(n);
        g.resize(n);
        res = 0;
        dfn = 0;
    }

    void add(int from, int to) {
        assert(0 <= from && from < n && 0 <= to && to < m);
        g[from].push_back(to);
    }

    bool dfs(int v) {
        vis[v] = dfn;
        for (int u : g[v]) {
            if (pb[u] == -1) {
                pb[u] = v;
                pa[v] = u;
                return true;
            }
        }
        for (int u : g[v]) {
            if (vis[pb[u]] != dfn && dfs(pb[u])) {
                pa[v] = u;
                pb[u] = v;
                return true;
            }
        }
        return false;
    }

    int solve() {
        while (true) {
            dfn++;
            int cnt = 0;
            for (int i = 0; i < n; i++) {
                if (pa[i] == -1 && dfs(i)) {
                    cnt++;
                }
            }
            if (cnt == 0) {
                break;
            }
            res += cnt;
        }
        return res;
    }
};

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);

    int n;
    cin >> n;

    vector<array<int, 2>>red(n), bule(n);
    for (auto &[x, y] : red)
        cin >> x >> y;
    for (auto &[x, y] : bule)
        cin >> x >> y;

    augment_path ans(n, n);
    for (int i = 0; i < n; i ++) {
        auto [rx, ry] = red[i];
        for (int j = 0; j < n; j ++) {
            auto [bx, by] = bule[j];
            if (rx < bx && ry < by) {
                ans.add(i, j);
            }
        }
    }

    cout << ans.solve() << '\n';

    return 0;
}

C - 😠

CodeForces - 1551D1

题意

给你 \(n\times m\) 矩阵的单元格,你有 \(\frac{nm}{2}\) 个 1 × 2 的多米诺骨牌,现要求你将其中 k 个水平放置,其余垂直放置,问是否可行。(保证 \(n\times m\) 一定是偶数)

思路

对 n,m 分奇偶讨论:

  • n,m 都为偶数时:\(n\times m\) 一定可以拆分成 \(\frac{n}{2}\times \frac{m}{2}\) 个 2 × 2 的正方形,这时只要判断一下 k 是否为偶数即可。
  • n 为奇数,m 为偶数:
    • 考虑转换为第一种情况。
    • 当我们把第 1 行单独填满时,剩下的就和第一种情况一样了,考虑把长度为 m 的一行需要 \(\frac{m}{2}\) 个水平骨牌,所以判断剩下的 \((k-\frac{m}{2})\) 是否为偶数即可,当然负数也不行,否则第一行也填不满。
  • n 为偶数,m 为奇数:
    • 同样是考虑转换为第一种情况。
    • 当我们把这个矩阵竖起来看,它就是第二种情况了,此时需要把长度为 n 的单独填满,但这个时候要求的是 \(\frac{n}{2}\) 个垂直骨牌,而我们有 \(\frac{nm}{2} - k\) 个垂直骨牌,于是就看 \((\frac{nm}{2}-k-\frac{n}{2})\) 是否为偶数即可,当然,负数也不行。

代码

#include<bits/stdc++.h>

using namespace std;

using i64 = long long;

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);

    int t;
    cin >> t;

    while (t--) {

        int n, m, k;
        cin >> n >> m >> k;

        if (n % 2 == 0 && m % 2 == 0) {
            puts(k & 1 ? "NO" : "YES");
        } else if (n & 1) {
            int ok = (k - m / 2);
            if (ok < 0) puts("NO");
            else puts(ok & 1 ? "NO" : "YES");
        } else {
            int ok = (n * m / 2 - k - n / 2);
            if (ok < 0) puts("NO");
            else puts(ok & 1 ? "NO" : "YES");
        }
    }

    return 0;
}

D - 😓

AtCoder - abc123_d

题意

给你 3 个序列,你需要从中选出其三元组之和最大的前 k 个。

思路

先预处理出两个序列和最大的前 k 个,因为保证 k 最大只有三千,所以又可以把这 k 个拿去和第三个序列组合,再次选出最大的前 k 个即可。

代码

#include<bits/stdc++.h>

using namespace std;

using i64 = long long;

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);

    int x, y, z, k;
    cin >> x >> y >> z >> k;

    const int N = 3000;
    vector<i64> X(x + 1), Y(y + 1), Z(z + 1);
    for (int i = 1; i <= x; i ++)
        cin >> X[i];
    for (int i = 1; i <= y; i ++)
        cin >> Y[i];
    for (int i = 1; i <= z; i ++)
        cin >> Z[i];

    vector<i64> s;

    priority_queue<i64, vector<i64>, greater<>> Q;
    for (int i = 1; i <= x; i ++) {
        for (int j = 1; j <= y; j ++) {
            i64 t = X[i] + Y[j];
            if (Q.size() < k) {
                Q.push(t);
            } else if (Q.top() < t) {
                Q.pop();
                Q.push(t);
            }
        }
    }

    vector<i64> XY;
    while (Q.size()) {
        XY.push_back(Q.top());
        Q.pop();
    }

    for (int i = 0; i < XY.size(); i ++) {
        for (int j = 1; j <= z; j ++) {
            i64 t = XY[i] + Z[j];
            s.push_back(t);
        }
    }

    sort(s.begin(), s.end(), greater<>());

    int cnt = 0;
    for (auto i : s) {
        cout << i << '\n';
        if (++cnt >= k) break;
    }

    return 0;
}

E - 🐔

AtCoder - arc078_b

题意

给你一棵树, Fennec 从 1 开始染色,可以把与黑色点相邻但未被染色的点染成黑色,Snuke 同理,不过是从 n 开始,将点染成白色,开始只有点 1 为黑色,n 为白色, F 先手,问你在双方最优染色方案下,谁会获胜。

思路

对于树上某一个点,如果 F 走到这里路程小于等于 S 走到的这里路程,那么 F 一定可以在 S 先到达这里时染色,等于也可以是因为 F 有先手优势,所有可以用两次 dfs 求出 F 和 S 到达所有点的距离,对于每个点比较双方的路程更新双方能够染色的点,最后比较一下即可。

代码

#include<bits/stdc++.h>

using namespace std;

using i64 = long long;

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);

    int n;
    cin >> n;

    vector g(n + 1, vector<int>());
    for (int i = 1; i < n; i ++) {
        int u, v;
        cin >> u >> v;
        g[u].push_back(v);
        g[v].push_back(u);
    }

    vector dis(2, vector<int>(n + 1));
    auto dfs = [&](auto & self, int u, int fa, int id)->void{
        for (auto v : g[u]) {
            if (v == fa) continue;
            dis[id][v] = dis[id][u] + 1;
            self(self, v, u, id);
        }
    };

    dfs(dfs, 1, 0, 0);
    dfs(dfs, n, 0, 1);

    int num0 = 0, num1 = 0;
    for (int i = 1; i <= n; i ++) {
        if (dis[0][i] <= dis[1][i]) num0 ++;
        else num1 ++;
    }

    puts(num0 > num1 ? "Fennec" : "Snuke");

    return 0;
}

F - 🐂

CodeForces - 448D

题意

你有一个 \(n\times m\) 的矩阵,定义 \(A_{i,j} = i \times j\),问你第 k 大元素为多少。

思路

1 2 3 4 5 6
2 4 6 8 10 12
3 6 9 12 15 18
4 8 12 16 20 24
5 10 15 20 25 30
6 12 18 24 30 36

对于这样一个 6 × 6 的表格,如果 k 等于 10 ,那么通过观察可以发现,每行最多只有 \(max(\frac{k}{i},m)\) 个元素比 k 小,又因为对于值越大的元素,小于它的元素是越多的,因此答案满足单调性,二分答案即可。

代码

#include<bits/stdc++.h>

using namespace std;

using i64 = long long;

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);

    i64 n, m, k;
    cin >> n >> m >> k;

    auto check = [&](i64 mid)->bool{
        i64 res = 0;
        for (int i = 1; i <= n; i ++) {
            res += min(mid / i , m);
            if (res >= k) return true;
        }
        return res >= k;
    };

    i64 l = 1, r = n * m, ans = -1;
    while (l <= r) {
        i64 mid = l + r >> 1;
        if (check(mid)) r = mid - 1, ans = mid;
        else l = mid + 1;
    }

    cout << ans << '\n';

    return 0;
}

G - 😄

CodeForces - 1618F

题意

给你两个数 x,y,你可以对 x 进行两种操作任意次:

  • 在 x 的二进制后添加 0 ,然后翻转二进制(去除前导零)。

  • 在 x 的二进制后添加 1 ,然后翻转二进制(去除前导零)。

问你最后能否变成 y 。

思路

一个数加 0 后翻转,和没加 0 时一样的,因为翻转后都会去除前导零。

然后直接对两种操作进行一个搜索即可,当搜的数 x 大于 y 的两倍时退出即可,以此剪枝,但是这样可能会误判 (8,1)这种数据,但是结合以上结论来看,我们可以再反着搜一次,即对翻转后的 x 再 dfs 一次即可。
注意过程可能爆longlong。

代码

#include<bits/stdc++.h>

using namespace std;

using i64 = long long;

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);

    i64 x, y;
    cin >> x >> y;

    auto re = [](i64 x)->i64{
        i64 res = 0;

        vector<int> mi;
        while (x) {
            mi.push_back(x & 1);
            x >>= 1;
        }

        while (!mi.back()) mi.pop_back();
        reverse(mi.begin(), mi.end());

        for (int i = 0; i < mi.size(); i ++) {
            if (mi[i])
                res += (1ll << i);
        }

        return res;
    };

    unordered_map<i64, bool> vis;
    auto dfs = [&](auto & self, i64 z) {
        if (z == y) {
            cout << "YES\n";
            exit(0);
        }
        if (vis[z] || (__int128)z >= ((__int128)y << 1)) {
            return ;
        }

        vis[z] = 1;

        self(self, re(z));
        z <<= 1, z ++;
        self(self, re(z));
    };

    dfs(dfs, x);
    dfs(dfs, re(x));

    cout << "NO\n";

    return 0;
}

H - 

AtCoder - abc265_a

题意

一家水果店出售苹果。您可以按照任意顺序多次执行以下操作:

  • 用 X 日元(日本的货币)买一个苹果。
  • 用 Y 日元买三个苹果。

您需要支付最少多少日元才能获得确切 N 的苹果?

思路

比较下单买一个苹果和一次买三个苹果的价格即可。

代码

#include<bits/stdc++.h>

using namespace std;

using i64 = long long;

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);

    int x, y, n;
    cin >> x >> y >> n;

    if (x * 3 <= y) {
        cout << n * x << '\n';
    } else {
        cout << n / 3 * y + n % 3 * x << '\n';
    }

    return 0;
}

I - 😚

AtCoder - abc207_e

题意

给定一个序列 𝑎,问将其划分成若干段,满足第 𝑖 段的和是 𝑖 的倍数的划分方案的个数。

思路

考虑 dp。

\(dp_{i,j}\) 表示前 i 个数分成 j 段的方案数,可以得到以下转移方程:

\[dp_{i,j}=\sum_{k=j-1}^{i-1}dp_{k,j-1}[\sum_{p=k+1}^{i}a_p\bmod j=0] \]

复杂度 \(O(n^3)\),显然超时。

考虑优化,用前缀和优化,可以转变为 $(sum_i-sum_k) \bmod j=0 $,即 \(sum_i \equiv sum_k \pmod{j}\)

\(g_{k,j}\) 为当 \(sum_i \bmod j = k\) 时,分成 j 段的方案数,则原式转化为 \(dp_{i,j}=g_{sum_i\bmod j,j-1}\),由于 \(dp_{i,j-1}\)会对 \(g_{sum_{i+1}\bmod j,j-1}\) 产生贡献,所以当我们使用完 \(g_{sum_i\bmod j,j-1}\) 后,需要加上 \(dp_{i,j-1}\)

枚举方案由于段数是若干段,即未知的,所以应该从段数即 j 开始枚举 n 个数在当前段数下产生的总方案数来进行转移。

代码

#include<bits/stdc++.h>

using namespace std;

using i64 = long long;

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);

    const i64 mod = 1e9 + 7;

    int n;
    cin >> n;

    vector dp(n + 1, vector<i64>(n + 1));
    vector g(n + 1, vector<i64>(n + 1));
    vector<i64> a(n + 1), pre(n + 1);

    for (int i = 1; i <= n; i ++) {
        cin >> a[i];
        pre[i] = pre[i - 1] + a[i];
    }

    g[0][0] = 1;
    for (int j = 1; j <= n; j ++) {
        for (int i = 1; i <= n; i ++) {
            auto& sum = g[pre[i] % j][j - 1];
            dp[i][j] = sum % mod;
            sum = (sum + dp[i][j - 1]) % mod;
        }
    }

    i64 ans = 0;
    for (int i = 1; i <= n; i ++)
        ans = (ans + dp[n][i]) % mod;

    cout << ans << '\n';

    return 0;
}

posted @ 2024-07-13 22:07  Ke_scholar  阅读(58)  评论(0编辑  收藏  举报