Problem A - Determinant

按题意来进行直接计算

时间复杂度:AtCoder Beginner Contest 184 题解 - 图1#card=math&code=%5Cmathcal%7BO%7D%281%29)

  1. int main() {
  2. ios_base::sync_with_stdio(false), cin.tie(0);
  3. int a, b, c, d;
  4. cin >> a >> b >> c >> d;
  5. cout << a * d - b * c;
  6. return 0;
  7. }

Problem B - Quizzes

模拟

时间复杂度:AtCoder Beginner Contest 184 题解 - 图2#card=math&code=%5Cmathcal%7BO%7D%28N%29)

  1. int main() {
  2. ios_base::sync_with_stdio(false), cin.tie(0);
  3. int N, X;
  4. string S;
  5. cin >> N >> X >> S;
  6. for (auto c : S) {
  7. if (c == 'x' and X > 0) X--;
  8. else if (c == 'o')
  9. X++;
  10. }
  11. cout << X << "\n";
  12. return 0;
  13. }

Problem C - Super Ryuma

题意:

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

  • AtCoder Beginner Contest 184 题解 - 图3
  • AtCoder Beginner Contest 184 题解 - 图4
  • AtCoder Beginner Contest 184 题解 - 图5

请找出从 $(r1, c1) $ 移动到 AtCoder Beginner Contest 184 题解 - 图6#card=math&code=%28r2%2C%20c2%29) 的最少的移动步数。

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

先分析移动方法

规则一:AtCoder Beginner Contest 184 题解 - 图7

当前位置为 AtCoder Beginner Contest 184 题解 - 图8#card=math&code=%28a%2C%20b%29)。

1、我们向右下移动一格,对应的坐标为 AtCoder Beginner Contest 184 题解 - 图9#card=math&code=%28a-1%2C%20b%2B1%29);

2、我们向右下移动 n 格,对应的坐标为 AtCoder Beginner Contest 184 题解 - 图10#card=math&code=%28a-n%2C%20b%2Bn%29);

3、我们向左上移动一格,对应的坐标为 AtCoder Beginner Contest 184 题解 - 图11#card=math&code=%28a%2B1%2C%20b-1%29);

4、我们向左上移动 n 格,对应的坐标为 AtCoder Beginner Contest 184 题解 - 图12#card=math&code=%28a%2Bn%2C%20b-%20n%29);
我们可以发现,满足条件 c+d=a+b,也就是满足条件一。也就是副对角线方向运动,如下图所示。

AtCoder Beginner Contest 184 题解 - 图13

规则二:AtCoder Beginner Contest 184 题解 - 图14

当前位置为 AtCoder Beginner Contest 184 题解 - 图15#card=math&code=%28a%2C%20b%29)。

1、我们向左下移动一格,对应的坐标为 AtCoder Beginner Contest 184 题解 - 图16#card=math&code=%28a%2B1%2C%20b%2B1%29);

2、我们向左下移动 n 格,对应的坐标为 AtCoder Beginner Contest 184 题解 - 图17#card=math&code=%28a%2Bn%2C%20b%2Bn%29);

3、我们向右上移动一格,对应的坐标为 AtCoder Beginner Contest 184 题解 - 图18#card=math&code=%28a-1%2C%20b-1%29);

4、我们向右上移动 n 格,对应的坐标为 AtCoder Beginner Contest 184 题解 - 图19#card=math&code=%28a-n%2C%20b-%20n%29);

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

AtCoder Beginner Contest 184 题解 - 图20

规则三:AtCoder Beginner Contest 184 题解 - 图21

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

AtCoder Beginner Contest 184 题解 - 图22

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

样例数据 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. 起点和终点重合,总步数为 AtCoder Beginner Contest 184 题解 - 图23
  2. 一步可到达(共对角线或曼哈顿距离不超过 AtCoder Beginner Contest 184 题解 - 图24),总步数为 AtCoder Beginner Contest 184 题解 - 图25
  3. 走两次对角线,设此时中间点为AtCoder Beginner Contest 184 题解 - 图26#card=math&code=%28r%2Cc%29),可得到关于 AtCoder Beginner Contest 184 题解 - 图27AtCoder Beginner Contest 184 题解 - 图28的二元一次方程组,判断其是否有整数解(其实就是判断奇偶)。如果有整数解,总步数为 AtCoder Beginner Contest 184 题解 - 图29
  4. 枚举起点的邻近点,然后判断是否一步可到达。如果可到达,则总步数为 AtCoder Beginner Contest 184 题解 - 图30
  5. 其他所有情况都可以通过移动到一个相邻的格子转化为第三种情况,从而总步数为 AtCoder Beginner Contest 184 题解 - 图31

时间复杂度 AtCoder Beginner Contest 184 题解 - 图32#card=math&code=%5Cmathcal%7BO%7D%281%29)。

  1. using ll = long long;
  2. int main() {
  3. ios_base::sync_with_stdio(false), cin.tie(0);
  4. ll a, b, c, d;
  5. cin >> a >> b >> c >> d;
  6. c -= a, d -= b;
  7. c = llabs(c), d = llabs(d);
  8. if (c == 0 && d == 0) cout << 0 << "\n";
  9. else if (c == d || c + d <= 3)
  10. cout << 1 << "\n";
  11. else if ((c + d) % 2 == 0 || c + d <= 6 || llabs(c - d) <= 3)
  12. cout << 2 << "\n";
  13. else
  14. cout << 3 << "\n";
  15. return 0;
  16. }

Problem D - increment of coins

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

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

  1. #include <bits/stdc++.h>
  2. using namespace std;
  3. typedef long long ll;
  4. const int N = 1e2 + 10;
  5. double dp[N][N][N];
  6. int main() {
  7. ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
  8. int a, b, c;
  9. cin >> a >> b >> c;
  10. for (int i = 99; i >= a; i--)
  11. for (int j = 99; j >= b; j--)
  12. for (int k = 99; k >= c; k--) {
  13. // 令 t = x + y + z,减少代码量
  14. double t = i + j + k;
  15. dp[i][j][k] = i / t * (dp[i + 1][j][k] + 1) +
  16. j / t * (dp[i][j + 1][k] + 1) +
  17. k / t * (dp[i][j][k + 1] + 1);
  18. }
  19. cout << fixed << setprecision(9) << dp[a][b][c] << endl;
  20. }

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

Problem E - Third Avenue

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

  1. using ll = long long;
  2. const int dx[4] = {-1, 0, 1, 0}, dy[4] = {0, -1, 0, 1};
  3. const int INF = 0x3f3f3f3f;
  4. int H, W;
  5. int main() {
  6. ios_base::sync_with_stdio(false), cin.tie(0);
  7. cin >> H >> W;
  8. vector<string> a(H);
  9. int si, sj, gi, gj;
  10. vector<vector<pair<int, int>>> tele(26);
  11. for (int i = 0; i < H; ++i) {
  12. cin >> a[i];
  13. for (int j = 0; j < W; ++j) {
  14. if (a[i][j] == 'S') si = i, sj = j;
  15. if (a[i][j] == 'G') gi = i, gj = j;
  16. if (a[i][j] >= 'a' && a[i][j] <= 'z')
  17. tele[a[i][j] - 'a'].emplace_back(i, j);
  18. }
  19. }
  20. vector<bool> vis(26);
  21. vector<vector<int>> dist(H, vector<int>(W, INF));
  22. dist[si][sj] = 0;
  23. queue<pair<int, int>> q;
  24. q.emplace(si, sj);
  25. while (q.size()) {
  26. auto [i, j] = q.front();
  27. q.pop();
  28. if (i == gi and j == gj) {
  29. cout << dist[i][j] << "\n";
  30. return 0;
  31. }
  32. for (int k = 0; k < 4; ++k) {
  33. int ni = i + dx[k], nj = j + dy[k];
  34. if (ni < 0 or ni >= H or nj < 0 or nj >= W or dist[ni][nj] != INF or
  35. a[ni][nj] == '#')
  36. continue;
  37. dist[ni][nj] = dist[i][j] + 1;
  38. q.emplace(ni, nj);
  39. }
  40. if (a[i][j] >= 'a' and a[i][j] <= 'z' and !vis[a[i][j] - 'a']) {
  41. vis[a[i][j] - 'a'] = true;
  42. for (auto [ni, nj] : tele[a[i][j] - 'a']) {
  43. if (dist[ni][nj] == INF) {
  44. dist[ni][nj] = dist[i][j] + 1;
  45. q.emplace(ni, nj);
  46. }
  47. }
  48. }
  49. }
  50. cout << -1 << "\n";
  51. return 0;
  52. }

Problem F - Programming Contest

meet−in−the−middle(又称折半搜索、双向搜索)对于AtCoder Beginner Contest 184 题解 - 图33的搜索类型题目,一般都可以采用该算法进行优化,很稳很暴力。

我们可以将n分成2部分这样可以将AtCoder Beginner Contest 184 题解 - 图34 对于 AtCoder Beginner Contest 184 题解 - 图35 的可以将复杂度降到 AtCoder Beginner Contest 184 题解 - 图36左右 AtCoder Beginner Contest 184 题解 - 图37;
然后我们通过dfs将前半段和后半段的所有不大于T的数存起来,在枚举一个的时候,判断另一个。

  1. #include <bits/stdc++.h>
  2. using namespace std;
  3. const int N = 1 << 20, M = 1e9 + 7;
  4. const int INF = 0x7fffffff;
  5. int n, m, T;
  6. int a[N], b[N], c[N];
  7. void dfs(int l, int r, int v, int d[], int &num) {
  8. if (v > T) return;
  9. if (l == r) {
  10. d[++num] = v;
  11. return;
  12. }
  13. dfs(l + 1, r, v + a[l], d, num);
  14. dfs(l + 1, r, v, d, num);
  15. }
  16. int main() {
  17. ios_base::sync_with_stdio(false), cin.tie(0);
  18. cin >> n >> T;
  19. for (int i = 1; i <= n; i++) cin >> a[i];
  20. sort(a + 1, a + n + 1);
  21. int len = n / 2 + 1, b_num = 0;
  22. dfs(1, len, 0, b, b_num);
  23. sort(b + 1, b + b_num + 1);
  24. int c_num = 0;
  25. dfs(len, n + 1, 0, c, c_num);
  26. sort(c + 1, c + c_num + 1);
  27. int ptr = c_num, maxn = 0;
  28. for (int i = 1; i <= b_num; i++) {
  29. while (b[i] + c[ptr] > T) ptr--;
  30. maxn = max(maxn, b[i] + c[ptr]);
  31. }
  32. cout << maxn << "\n";
  33. return 0;
  34. }

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

  1. #include <iostream>
  2. #include <set>
  3. #include <vector>
  4. using namespace std;
  5. typedef long long ll;
  6. int main() {
  7. int n, t;
  8. cin >> n >> t;
  9. vector<int> a(n);
  10. for (int i = 0; i < n; ++i) cin >> a[i];
  11. set<int> L, R;
  12. L.insert(0), R.insert(0);
  13. int l = n / 2, r = n - l;
  14. for (int i = 0; i < (1 << l); ++i) {
  15. int s = 0;
  16. for (int j = 0; j < l; ++j) {
  17. if (i & (1 << j)) s += a[j];
  18. if (s > t) break;
  19. }
  20. if (s <= t) L.insert(s);
  21. }
  22. for (int i = 0; i < (1 << r); ++i) {
  23. int s = 0;
  24. for (int j = 0; j < r; ++j) {
  25. if (i & (1 << j)) s += a[l + j];
  26. if (s > t) break;
  27. }
  28. if (s <= t) R.insert(s);
  29. }
  30. int ans = 0;
  31. for (int li : L) {
  32. auto it = R.lower_bound(t + 1 - li);
  33. if (it != R.begin()) --it;
  34. if (li + *it <= t) ans = max(ans, li + *it);
  35. if (ans == t) break;
  36. }
  37. cout << ans << endl;
  38. }