AtCoder Beginner Contest 383

省流版
  • A. 模拟加水漏水即可
  • B. 枚举两个加湿器的位置,然后统计加湿的单元格数量即可
  • C. 从每个加湿器进行 \(BFS\) 即可
  • D. 考虑因子个数的计算,分情况枚举质因数即可
  • E. 考虑\(f\)函数的求法,从小到大加边,考虑每条边对答案的贡献即可
  • F. 对颜色排序,在\(01\)背包的基础上,新增一个不同颜色时的转移,维护颜色的最后一次出现的位置即可

A - Humidifier 1 (abc383 A)

题目大意

加湿器初始无水,但一旦有水,单位时间内水量就会减少 \(1\) 升。

现给定 \(N\) 次加水操作,第 \(i\) 次加水发生在时间 \(T_i\) ,加了 \(V_i\) 升水。问最后一次加完后加湿器中剩余的水量。

解题思路

时刻最多只有 \(100\) ,可以直接模拟。也可以直接按题意维护下次加水时的水量。

神奇的代码
#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;
    int w = 0;
    int la = 0;
    while (n--) {
        int t, x;
        cin >> t >> x;
        w = max(0, w - t + la);
        la = t;
        w += x;
    }
    cout << w << '\n';

    return 0;
}



B - Humidifier 2 (abc383 B)

题目大意

给定包含.#的二维网格,选择两个.放置加湿器,使得加湿器的加湿的单元格数量最大。

当且仅当单元格与至少一个加湿器的曼哈顿距离 \(D\) 在内时,该单元格被加湿。 \((i,j)\)\((i',j')\) 之间的曼哈顿距离定义为 \(|i - i'| + |j - j'|\)

解题思路

枚举两个加湿器的位置,然后统计加湿的单元格数量。时间复杂度为 \(O((HW)^2)\)

神奇的代码
#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 h, w, d;
    cin >> h >> w >> d;
    vector<string> s(h);
    for (auto& i : s)
        cin >> i;
    vector<array<int, 2>> pos;
    for (int i = 0; i < h; i++) {
        for (int j = 0; j < w; j++) {
            if (s[i][j] == '.') {
                pos.push_back({i, j});
            }
        }
    }
    auto inner = [&](int x, int y, int z) -> bool {
        auto [x1, y1] = pos[z];
        return abs(x1 - x) + abs(y1 - y) <= d;
    };
    auto solve = [&](int x, int y) -> int {
        int cnt = 0;
        for (int i = 0; i < h; i++) {
            for (int j = 0; j < w; j++) {
                cnt += s[i][j] == '.' && (inner(i, j, x) || inner(i, j, y));
            }
        }
        return cnt;
    };
    int ans = 0;
    for (int i = 0; i < pos.size(); i++) {
        for (int j = i + 1; j < pos.size(); j++) {
            ans = max(ans, solve(i, j));
        }
    }
    cout << ans << '\n';

    return 0;
}



C - Humidifier 3 (abc383 C)

题目大意

给定一个 \(H \times W\) 的网格,"#"为墙壁;"."为地板;"H"为放置了加湿器。

如果从至少一个加湿器单元格向上、向下、向左或向右移动最多 \(D\) 次而不经过墙壁,则该单元格被视为加湿单元格。

求加湿地板单元格的数量。

解题思路

上述移动方式,即从每个加湿器单元格进行 \(BFS\) ,直到移动次数到达 \(D\),途中经过的地板单元格均为加湿单元格。

神奇的代码
#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 h, w, d;
    cin >> h >> w >> d;
    vector<string> s(h);
    for (auto& i : s)
        cin >> i;
    queue<array<int, 2>> team;
    int ans = 0;
    vector<vector<int>> dis(h, vector<int>(w, h * w + 8));
    for (int i = 0; i < h; i++) {
        for (int j = 0; j < w; j++) {
            if (s[i][j] == 'H') {
                team.push({i, j});
                dis[i][j] = 0;
                ++ans;
            }
        }
    }
    array<int, 2> dir[] = {{0, 1}, {0, -1}, {1, 0}, {-1, 0}};
    while (!team.empty()) {
        auto [x, y] = team.front();
        team.pop();
        for (auto [dx, dy] : dir) {
            int nx = x + dx, ny = y + dy;
            if (nx >= 0 && nx < h && ny >= 0 && ny < w && s[nx][ny] != '#' &&
                dis[x][y] + 1 <= d && dis[nx][ny] > dis[x][y] + 1) {
                dis[nx][ny] = dis[x][y] + 1;
                team.push({nx, ny});
                ++ans;
            }
        }
    }
    cout << ans << '\n';

    return 0;
}



D - 9 Divisors (abc383 D)

题目大意

求恰好有 \(9\) 个因子的不大于 \(N\) 的正整数的个数。

解题思路

将一个数质因数分解,设 \(n = p_1^{a_1} \times p_2^{a_2} \times \cdots \times p_k^{a_k}\) ,则 \(n\) 的因子个数为 \((a_1 + 1) \times (a_2 + 1) \times \cdots \times (a_k + 1)\)

因此,如果一个数的因子个数为 \(9\) ,则其质因数分解后,只能有两种情况:

  • \(p_1^2 \times p_2^2\) ,此时 \(a_1 = a_2 = 2\)
  • \(p_1^8\) ,此时 \(a_1 = 8\)

由于 \(N \leq 4e12\),因此对于情况一,\(p_1p_2 \leq 2e6\),因此可以枚举 \(p_1\)\(p_2\) ,然后判断是否满足条件,其时间复杂度不会超过 \(O(2e6)\)

而情况二,同样直接枚举 \(p_1\) ,然后判断是否满足条件即可。

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

const LL p_max = 2E6 + 100;
LL pr[p_max], p_sz;
void get_prime() {
    static bool vis[p_max];
    for (int i = 2; i < p_max; i++) {
        if (!vis[i])
            pr[p_sz++] = i;
        for (int j = 0; j < p_sz; j++) {
            if (pr[j] * i >= p_max)
                break;
            vis[pr[j] * i] = 1;
            if (i % pr[j] == 0)
                break;
        }
    }
}

int main(void) {
    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);
    get_prime();
    LL n;
    cin >> n;
    LL half = ceil(sqrt(n));
    LL ans = 0;
    for (int i = 0; i < p_sz; i++) {
        for (int j = i + 1; j < p_sz; j++) {
            if (pr[i] * pr[j] > half)
                break;
            ans += (pr[i] * pr[i] * pr[j] * pr[j] <= n);
        }
        LL tmp = 1;
        int up = 0;
        for (; up < 8; ++up) {
            tmp *= pr[i];
            if (tmp > n)
                break;
        }
        ans += (up == 8);
    }
    cout << ans << '\n';

    return 0;
}



E - Sum of Max Matching (abc383 E)

题目大意

给定一张无向图,边有边权。给定两个长度为 \(K\) 的序列: \((A_1, A_2, \ldots, A_K)\)\((B_1, B_2, \ldots, B_K)\)

将序列 \(B\) 自由排列,使 \(\displaystyle \sum_{i=1}^{K} f(A_i, B_i)\) 最小。其中 \(f(x, y)\) 为从顶点 \(x\) 到顶点 \(y\) 的路径的最小路径权重。

一条路径的权重定义为路径中一条边的最大权重。

解题思路

考虑 \(f(x, y)\) 的定义,因为一条路径的权重定义为路径中一条边的最大权重,而两点的路径有很多条,我们要找最小权重的那条。

怎么求 \(f(x,y)\)呢?我们可以考虑加边,从边权最小的边开始加,这样,当两个点突然位于同一个连通块时(即有路径连通时),就说明这个边权就是这两个点路径的必经边,而由于我们是从小到大加边,所以这个边权就是这两个点路径的最小权重。
you

因此,我们可以将边按权重从小到大排序,然后依次加边,对于每条边,如果将两个不同的连通块合并,那么这两个连通块里的 \(A_i\)\(B_i\) 就可以连通,对应的连通点对 \((A_i, B_i)\) 数量就是该边权对答案的贡献。所有的边权对答案的贡献求和即为答案。

我们可以用并查集来维护连通性。

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

class dsu {
  public:
    vector<int> p;
    vector<int> a;
    vector<int> b;
    int n;

    dsu(int _n) : n(_n) {
        p.resize(n);
        a.resize(n);
        b.resize(n);
        iota(p.begin(), p.end(), 0);
        fill(a.begin(), a.end(), 0);
        fill(b.begin(), b.end(), 0);
    }

    inline int get(int x) { return (x == p[x] ? x : (p[x] = get(p[x]))); }

    inline LL unite(int x, int y, int w) {
        x = get(x);
        y = get(y);
        LL ret = 0;
        if (x != y) {
            int c = min(a[x], b[y]);
            a[x] -= c;
            b[y] -= c;
            ret += 1ll * c * w;
            c = min(a[y], b[x]);
            a[y] -= c;
            b[x] -= c;
            ret += 1ll * c * w;

            p[x] = y;
            a[y] += a[x];
            b[y] += b[x];
        }
        return ret;
    }
};

int main(void) {
    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);
    int n, m, k;
    cin >> n >> m >> k;
    vector<array<int, 3>> edge(m);
    for (auto& [u, v, w] : edge) {
        cin >> u >> v >> w;
        u--;
        v--;
    }
    dsu d(n);
    for (int i = 0; i < k; i++) {
        int x;
        cin >> x;
        d.a[x - 1]++;
    }
    for (int i = 0; i < k; i++) {
        int x;
        cin >> x;
        d.b[x - 1]++;
    }
    vector<int> id(m);
    iota(id.begin(), id.end(), 0);
    sort(id.begin(), id.end(),
         [&](int i, int j) { return edge[i][2] < edge[j][2]; });
    LL ans = 0;
    for (auto i : id) {
        auto [u, v, w] = edge[i];
        ans += d.unite(u, v, w);
    }
    cout << ans << '\n';

    return 0;
}



F - Diversity (abc383 F)

题目大意

给定 \(N\) 种商品,每种商品有价格 \(P_i\) ,效用值 \(U_i\) ,颜色 \(C_i\)

选一些商品,使得总价不超过 \(X\) ,满意度最大化。满意度定义为 \(S + T \times K\) ,其中 \(S\) 是所选商品的效用总和, \(T\) 是所选商品中不同颜色的数量, \(K\) 是一个给定的常数。

解题思路

如果没有颜色,那么这是一个经典的 \(01\) 背包问题。设 \(dp[i][j]\) 表示前 \(i\) 种商品,总价不超过 \(j\) 的情况下的最大效用值。

而颜色在这里会多了额外贡献。维护已经选了什么颜色的复杂度是指数级别的。我们可以考虑对商品按照颜色进行排序。

假设商品颜色为 \(1,1,1,2,2,2,...\),比如我们在考虑第 \(5\) 个商品,它可以从 \(dp[4]\)转移过来,这和正常的 \(01\) 背包没有区别。同时,它也可以从\(dp[3]\)转移过来,但此时会有一个额外的 \(k\)的贡献,因为这是第一次选择颜色 \(2\)的商品。

因此对于当前的商品,我们额外维护一个位置,该位置是上一个颜色最后一次出现的位置\(last\),这样在原来的 \(01\) 背包的基础上,再加一个从 \(dp[last]\)转移过来的状态即可。

由于转移时第一维仅依赖上一次,所以代码里将第一维的维滚动压缩掉,同时用 \(dpc\) 数组来维护 \(dp[last]\)的值。时间复杂度为 \(O(NX)\)

神奇的代码
#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, x, k;
    cin >> n >> x >> k;
    vector<array<int, 3>> a(n);
    for (auto& [p, u, c] : a)
        cin >> p >> u >> c;
    vector<int> id(n);
    iota(id.begin(), id.end(), 0);
    sort(id.begin(), id.end(), [&](int i, int j) { return a[i][2] < a[j][2]; });
    vector<LL> dp(x + 1, 0);
    vector<LL> dpc(x + 1, 0);
    int last = 0;
    for (auto i : id) {
        auto [p, u, c] = a[i];
        if (last != c)
            dpc = dp;
        vector<LL> dp2 = dp;
        for (int j = 0; j <= x; j++) {
            if (j >= p) {
                dp2[j] = max(dp2[j], dp[j - p] + u);
                dp2[j] = max(dp2[j], dpc[j - p] + u + k);
            }
        }
        last = c;
        dp.swap(dp2);
    }
    LL ans = *max_element(dp.begin(), dp.end());
    cout << ans << '\n';

    return 0;
}



G - Bar Cover (abc383 G)

题目大意

问题陈述

有一行 \(N\) 单元格。每个单元格包含一个整数 \(A_i\)

\(\lfloor \frac{N}{K} \rfloor\) 块瓷砖,每块瓷砖可以覆盖 \(K\) 个连续的单元格。

对于每一个 \(i = 1, \ldots, \lfloor \frac{N}{K} \rfloor\) 解决下面的问题:

  • 当恰好放置 \(i\) 个瓷砖,且相互不重叠,求被覆盖单元格中数字和的最大值。

解题思路

<++>

神奇的代码



posted @ 2024-12-14 15:14  ~Lanly~  阅读(34)  评论(0编辑  收藏  举报