AtCoder Beginner Contest 184 题解

Problem A - Determinant

按题意来进行直接计算

时间复杂度:\(\mathcal{O}(1)\)

int main() {
    ios_base::sync_with_stdio(false), cin.tie(0);
    int a, b, c, d;
    cin >> a >> b >> c >> d;
    cout << a * d - b * c;
    return 0;
}

Problem B - Quizzes

模拟

时间复杂度:\(\mathcal{O}(N)\)

int main() {
    ios_base::sync_with_stdio(false), cin.tie(0);
    int N, X;
    string S;
    cin >> N >> X >> S;
    for (auto c : S) {
        if (c == 'x' and X > 0) X--;
        else if (c == 'o')
            X++;
    }
    cout << X << "\n";
    return 0;
}

Problem C - Super Ryuma

题意:

有一个无限的二维网格,在坐标 (r1, c1) 处有一个超级龙马,每次这个超级龙马可以移动如上图的位置。更加准确的说,当超级龙马在坐标 (a, b),它可以移动到坐标 (c, d) 只要满足下面的条件:

  • \(a+b=c+d\)
  • \(a−b=c−d\)
  • \(|a−c|+|b−d|≤3\)

请找出从 $(r1, c1) $ 移动到 \((r2, c2)\) 的最少的移动步数。

这里引用一下 努力的老周 的解法:

先分析移动方法

规则一:\(a+b=c+d\)

当前位置为 \((a, b)\)

1、我们向右下移动一格,对应的坐标为 \((a-1, b+1)\)

2、我们向右下移动 n 格,对应的坐标为 \((a-n, b+n)\)

3、我们向左上移动一格,对应的坐标为 \((a+1, b-1)\)

4、我们向左上移动 n 格,对应的坐标为 \((a+n, b- n)\)
我们可以发现,满足条件 c+d=a+b,也就是满足条件一。也就是副对角线方向运动,如下图所示。

规则二:\(a-b=c-d\)

当前位置为 \((a, b)\)

1、我们向左下移动一格,对应的坐标为 \((a+1, b+1)\)

2、我们向左下移动 n 格,对应的坐标为 \((a+n, b+n)\)

3、我们向右上移动一格,对应的坐标为 \((a-1, b-1)\)

4、我们向右上移动 n 格,对应的坐标为 \((a-n, b- n)\)

我们可以发现,满足条件 c-d=a-b,也就是满足条件二。也就是主对角线方向运动,如下图所示。

规则三:\(|a−c|+|b−d|≤3\)

自然就是图片中中间部分。如下图所示。

下面我们根据这个来分析一下样例数据。

样例数据 1

根据样例数据 1,我们需要从 (1, 1) 到 (5, 6)。

先用规则二,沿着主对角线移动,从 (1, 1) 移动到 (5, 5);

再用规则三,从 (5,5) 移动到 (5, 6)。

样例数据 2
根据样例数据 2,我们需要从 (1, 1) 到 (1, 200001)。

先用规则二,沿着主对角线移动,从 (1, 1) 移动到 (100001, 100001);

再用规则一,沿着副对角线移动,从 (100001, 100001) 移动到 (1, 200001)。

样例数据 3
根据样例数据 3,我们需要从 (2, 3) 到 (998244353, 998244853)。

先规则三,从 (2,3) 到 (3, 3);

再用规则一,沿着副对角线移动,从 (3, 3) 到 (-247, 253);

再用规则二,沿着主对角线移动,从 (-247, 253) 移动到 (998244353, 998244853)。

根据上面的分析,我们可以总结出,从 (r1, c1) 移动到 (r2, c2),超级龙马的移动可能有以下几种可能:

移动次数为 0 次:起点坐标和终点坐标重合,即 r1==r2 && c1==c2

移动次数为 1 次

也就是超级龙马可以根据任意一条规则从 (r1, c1) 移动到 (r2, c2)。这样,有三条规则可以满足这个要求。

根据规则一,可以得到条件为 r1+r2==c1+c2

根据规则二,可以得到条件为 r1-r2==c1-c2;

根据规则三,可以得到条件为 abs(r1-r2)+abs(c1-c2)<=3

移动次数为 2 次

这是一个组合问题,也就是超级龙马要使用两次规则。我们可以通过遍历也就是先按照规则三移动一次,再判断利用其他规则能否到达目的地即可。具体的实现可以参看下面的 AC 代码。

还有一个特殊情况是起点坐标之和和终点坐标之和奇偶性相同。参考样例输入 2。

移动次数为 3 次:剩下的情况就移动三次肯定可以到达。

个人做的时候的分析:

  1. 起点和终点重合,总步数为 \(0\)
  2. 一步可到达(共对角线或曼哈顿距离不超过 \(3\)),总步数为 \(1\)
  3. 走两次对角线,设此时中间点为\((r,c)\),可得到关于 \(r\)\(c\)的二元一次方程组,判断其是否有整数解(其实就是判断奇偶)。如果有整数解,总步数为 \(2\)
  4. 枚举起点的邻近点,然后判断是否一步可到达。如果可到达,则总步数为 \(2\)
  5. 其他所有情况都可以通过移动到一个相邻的格子转化为第三种情况,从而总步数为 \(3\)

时间复杂度 \(\mathcal{O}(1)\)

using ll = long long;
int main() {
    ios_base::sync_with_stdio(false), cin.tie(0);
    ll a, b, c, d;
    cin >> a >> b >> c >> d;
    c -= a, d -= b;
    c = llabs(c), d = llabs(d);
    if (c == 0 && d == 0) cout << 0 << "\n";
    else if (c == d || c + d <= 3)
        cout << 1 << "\n";
    else if ((c + d) % 2 == 0 || c + d <= 6 || llabs(c - d) <= 3)
        cout << 2 << "\n";
    else
        cout << 3 << "\n";
    return 0;
}

Problem D - increment of coins

题意:一个包里包含 X 个金币、Y 个银币、Z 个铜币。在包里钱币满足相 同颜色达到 100 之前,我们可以重复以下动作:随机选一种钱币,取出一枚, 再放入相同颜色钱币两枚。找出完成这些操作的期望值。

根据题目的意思,其实就是每次向包里随机加入一枚钱币,直到包里某种钱币数量达到 100。本题的核心是如何计算期望。本题属于标准的动态规划求期望问题。直接套用模板即可。

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 1e2 + 10;
double dp[N][N][N];
int main() {
    ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
    int a, b, c;
    cin >> a >> b >> c;
    for (int i = 99; i >= a; i--)
        for (int j = 99; j >= b; j--)
            for (int k = 99; k >= c; k--) {
                // 令 t = x + y + z,减少代码量
                double t = i + j + k;
                dp[i][j][k] = i / t * (dp[i + 1][j][k] + 1) +
                              j / t * (dp[i][j + 1][k] + 1) +
                              k / t * (dp[i][j][k + 1] + 1);
            }
    cout << fixed << setprecision(9) << dp[a][b][c] << endl;
}

这道题是去年写过一次:Here.

Problem E - Third Avenue

经典BFS,但要注意同一种类型的传送点只考虑一次。

using ll = long long;
const int dx[4] = {-1, 0, 1, 0}, dy[4] = {0, -1, 0, 1};
const int INF = 0x3f3f3f3f;
int H, W;
int main() {
    ios_base::sync_with_stdio(false), cin.tie(0);
    cin >> H >> W;
    vector<string> a(H);
    int si, sj, gi, gj;
    vector<vector<pair<int, int>>> tele(26);
    for (int i = 0; i < H; ++i) {
        cin >> a[i];
        for (int j = 0; j < W; ++j) {
            if (a[i][j] == 'S') si = i, sj = j;
            if (a[i][j] == 'G') gi = i, gj = j;
            if (a[i][j] >= 'a' && a[i][j] <= 'z')
                tele[a[i][j] - 'a'].emplace_back(i, j);
        }
    }

    vector<bool> vis(26);
    vector<vector<int>> dist(H, vector<int>(W, INF));
    dist[si][sj] = 0;
    queue<pair<int, int>> q;
    q.emplace(si, sj);
    while (q.size()) {
        auto [i, j] = q.front();
        q.pop();
        if (i == gi and j == gj) {
            cout << dist[i][j] << "\n";
            return 0;
        }

        for (int k = 0; k < 4; ++k) {
            int ni = i + dx[k], nj = j + dy[k];
            if (ni < 0 or ni >= H or nj < 0 or nj >= W or dist[ni][nj] != INF or
                a[ni][nj] == '#')
                continue;
            dist[ni][nj] = dist[i][j] + 1;
            q.emplace(ni, nj);
        }

        if (a[i][j] >= 'a' and a[i][j] <= 'z' and !vis[a[i][j] - 'a']) {
            vis[a[i][j] - 'a'] = true;
            for (auto [ni, nj] : tele[a[i][j] - 'a']) {
                if (dist[ni][nj] == INF) {
                    dist[ni][nj] = dist[i][j] + 1;
                    q.emplace(ni, nj);
                }
            }
        }
    }
    cout << -1 << "\n";
    return 0;
}

Problem F - Programming Contest

meet−in−the−middle(又称折半搜索、双向搜索)对于\(n \le 40\)的搜索类型题目,一般都可以采用该算法进行优化,很稳很暴力。

我们可以将n分成2部分这样可以将\(2^n \to 2 \times2^{\frac{n}2}\) 对于 \(n=40\) 的可以将复杂度降到 \(nlogn\)左右 \(n->2^{20}\);
然后我们通过dfs将前半段和后半段的所有不大于T的数存起来,在枚举一个的时候,判断另一个。

#include <bits/stdc++.h>
using namespace std;
const int N = 1 << 20, M = 1e9 + 7;
const int INF = 0x7fffffff;
int n, m, T;
int a[N], b[N], c[N];

void dfs(int l, int r, int v, int d[], int &num) {
    if (v > T) return;
    if (l == r) {
        d[++num] = v;
        return;
    }
    dfs(l + 1, r, v + a[l], d, num);
    dfs(l + 1, r, v, d, num);
}

int main() {
    ios_base::sync_with_stdio(false), cin.tie(0);
    cin >> n >> T;
    for (int i = 1; i <= n; i++) cin >> a[i];
    sort(a + 1, a + n + 1);
    int len = n / 2 + 1, b_num = 0;
    dfs(1, len, 0, b, b_num);
    sort(b + 1, b + b_num + 1);
    int c_num = 0;
    dfs(len, n + 1, 0, c, c_num);
    sort(c + 1, c + c_num + 1);
    int ptr = c_num, maxn = 0;
    for (int i = 1; i <= b_num; i++) {
        while (b[i] + c[ptr] > T) ptr--;
        maxn = max(maxn, b[i] + c[ptr]);
    }
    cout << maxn << "\n";
    return 0;
}

另外可以使用set容器,比较慢

#include <iostream>
#include <set>
#include <vector>

using namespace std;
typedef long long ll;
int main() {
    int n, t;
    cin >> n >> t;
    vector<int> a(n);
    for (int i = 0; i < n; ++i) cin >> a[i];
    set<int> L, R;
    L.insert(0), R.insert(0);
    int l = n / 2, r = n - l;
    for (int i = 0; i < (1 << l); ++i) {
        int s = 0;
        for (int j = 0; j < l; ++j) {
            if (i & (1 << j)) s += a[j];
            if (s > t) break;
        }
        if (s <= t) L.insert(s);
    }
    for (int i = 0; i < (1 << r); ++i) {
        int s = 0;
        for (int j = 0; j < r; ++j) {
            if (i & (1 << j)) s += a[l + j];
            if (s > t) break;
        }
        if (s <= t) R.insert(s);
    }
    int ans = 0;
    for (int li : L) {
        auto it = R.lower_bound(t + 1 - li);
        if (it != R.begin()) --it;
        if (li + *it <= t) ans = max(ans, li + *it);
        if (ans == t) break;
    }
    cout << ans << endl;
}
posted @ 2021-03-26 19:59  RioTian  阅读(100)  评论(0编辑  收藏  举报