AtCoder Beginner Contest 中的小思维题

1|0078D


https://atcoder.jp/contests/abc078/tasks/arc085_b

问题陈述

我们有一副由 N 张牌组成的扑克牌。每张牌上都写着一个整数。从最上面开始的第 i 张牌上的整数是 ai

两个人 X 和 Y 将用这副扑克牌玩一个游戏。最初,X 手中有一张写有 Z 的牌,Y 手中有一张写有 W 的牌。然后,从 X 开始,他们将交替进行以下操作:

  • 从牌顶抽若干张牌。然后,丢弃手中的牌,保留最后抽出的牌。这里至少要抽出一张牌。

当牌组中没有牌时,游戏结束。游戏的得分是两位玩家手中牌上所写整数的绝对差值。

X 玩游戏的目的是使得分最大,而 Y 玩游戏的目的是使得分最小。游戏的得分是多少?

思路

其实可以想到就是,X 能拿到的只有倒数第二个或者倒数第一个,因为如果 X 选择了中间的任何一个,Y 的都可以剩下一些更差迫使 X 丢弃当前选择的。所以考虑一下选择哪一个更有就好了。要特判一下就是如果 n=1 则只能选择倒数一个。

#include <bits/stdc++.h> using namespace std; using i32 = int32_t; using i64 = long long; using i128 = __int128; #define int i64 using vi = vector<int>; i32 main() { ios::sync_with_stdio(false), cin.tie(nullptr); int n, z, w; cin >> n >> z >> w; vi a(n + 1); for(int i = 1; i <= n; i ++) cin >> a[i]; if(n == 1){ cout << abs(a[n] - w); }else { cout << max(abs(a[n] - a[n-1]), abs(a[n] - w)); } return 0; }

2|0091C


2|1贪心


把红色点从小到大排序,蓝色点从大到小排序,然后对于每个红色点,找到所有满足的蓝色点中 y 最小的。

#include <bits/stdc++.h> using namespace std; using i32 = int32_t; using i64 = long long; #define int i64 using vi = vector<int>; using pii = pair<int, int>; const int inf = 1e9; i32 main() { ios::sync_with_stdio(false), cin.tie(nullptr); int n; cin >> n; vector<pii> a(n), b(n); for (auto &[x, y]: a) cin >> x >> y; for (auto &[x, y]: b) cin >> x >> y; sort(a.begin(), a.end(), greater<>()); sort(b.begin(), b.end()); vi vis(n); for (auto &[ax, ay]: a) { int p = -1; for (int j = 0; j < n; j++) { if (vis[j]) continue; if (b[j].first <= ax or b[j].second <= ay) continue; if (p == -1) p = j; else if (p != -1 and b[j].second < b[p].second) p = j; } if (p != -1) vis[p] = 1; } cout << accumulate(vis.begin(), vis.end(), 0ll); return 0; }

2|2二分图最大匹配


直接上匈牙利算法

#include <bits/stdc++.h> using namespace std; using i32 = int32_t; using i64 = long long; #define int i64 using vi = vector<int>; using pii = pair<int, int>; const int inf = 1e9; vector<vi> e; vector<bool> vis; vi p; int n; bool match(int x) { for (auto y: e[x]) { if (vis[y]) continue; vis[y] = 1; if (p[y] == 0 or match(p[y])) { p[y] = x; return true; } } return false; } int Hungarian() { int cnt = 0; p.resize(n + n + 1); for (int i = 1; i <= n; i++) { vis = vector<bool>(n + n + 1); if (match(i)) cnt++; } return cnt; } i32 main() { ios::sync_with_stdio(false), cin.tie(nullptr); cin >> n; vector<pii> a(n + n + 1); for (int i = 1; i <= n + n; i++) cin >> a[i].first >> a[i].second; e.resize(n + n + 1); for (int i = 1; i <= n; i++) for (int j = n + 1; j <= n + n; j++) if (a[i].first < a[j].first and a[i].second < a[j].second) e[i].push_back(j), e[j].push_back(i); cout << Hungarian() << "\n"; return 0; }

3|0067D


两个人的最优策略都是优先占领 1 到 n 路径上的点,因为被占领的点,其子树也不能被占领。最后统计一下每个人最多可以占领多少个,比较一下即可。

#include <bits/stdc++.h> using namespace std; using i32 = int32_t; using i64 = long long; #define int i64 using vi = vector<int>; using pii = pair<int, int>; const int inf = 1e9; i32 main() { ios::sync_with_stdio(false), cin.tie(nullptr); int n; cin >> n; vector<vi> e(n + 1); for (int i = 1, x, y; i < n; i++) cin >> x >> y, e[x].push_back(y), e[y].push_back(x); vi dis1(n + 1, inf); dis1[1] = 0; queue<int> q; q.push(1); while (not q.empty()) { int x = q.front(); q.pop(); for (auto y: e[x]) { if (dis1[y] < dis1[x] + 1) continue; dis1[y] = dis1[x] + 1, q.push(y); } } vi dis2(n + 1, inf); dis2[n] = 0; q.push(n); while (not q.empty()) { int x = q.front(); q.pop(); for (auto y: e[x]) { if (dis2[y] < dis2[x] + 1) continue; dis2[y] = dis2[x] + 1, q.push(y); } } vi color(n + 1); queue<int> q1, q2; for (int i = 1; i <= n; i++) { if (dis1[i] + dis2[i] != dis1[n]) continue; if (dis1[i] <= dis2[i]) q1.push(i); else q2.push(i), color[i] = 2; } while (not q1.empty()) { int x = q1.front(); q1.pop(); for (auto y: e[x]) { if (color[y]) continue; color[y] = 1, q1.push(y); } } while (not q2.empty()) { int x = q2.front(); q2.pop(); for (auto y: e[x]) { if (color[y]) continue; color[y] = 2, q2.push(y); } } vi cnt(3); for (int i = 1; i <= n; i++) cnt[color[i]]++; if (cnt[1] > cnt[2]) cout << "Fennec"; else cout << "Snuke"; return 0; }

4|0123D


虽然枚举的种类是 109 但是注意到 k 最大是 3000,因此可以暴力枚举 A,B 然后取前 K 个,然后再暴力枚举即可。

#include <bits/stdc++.h> using namespace std; using i32 = int32_t; using i64 = long long; #define int i64 using vi = vector<int>; const int inf = 1e9; i32 main() { ios::sync_with_stdio(false), cin.tie(nullptr); int x, y, z, k; cin >> x >> y >> z>> k; vi a(x), b(y), c(z); for (auto &i: a) cin >> i; for (auto &i: b) cin >> i; for (auto &i: c) cin >> i; vi ab; for (const auto &ai: a) for (const auto &bi: b) ab.push_back(ai + bi); sort(ab.begin(), ab.end() , greater<>()); ab.resize(min((int) ab.size(), k)); vi abc; for (const auto &abi: ab) for (const auto &ci: c) abc.push_back(abi + ci); sort(abc.begin(), abc.end(), greater<>()); abc.resize(min((int) abc.size(), k)); for(const auto abci : abc) cout << abci << "\n"; return 0; }

5|0046D


可以想到的是能出布就出布,因为出布不会减分,但是出石头可能会减分。所以出的顺序应该是 gpgpgp

#include <bits/stdc++.h> using namespace std; using i32 = int32_t; using i64 = long long; using i128 = __int128; #define int i64 using vi = vector<int>; i32 main() { ios::sync_with_stdio(false), cin.tie(nullptr); string s; cin >> s; int res = 0; for(int i = 1; i < s.size(); i += 2) res += (s[i] == 'g'); for(int i = 0; i < s.size(); i += 2) res -= (s[i] == 'p'); cout << res; return 0; }

6|0096D


考虑只选择个位为 1 的质数,这样的话五个数的和一定为 5 的倍数。

#include <bits/stdc++.h> using namespace std; using i32 = int32_t; using i64 = long long; using i128 = __int128; #define int i64 using vi = vector<int>; bool isPrime(int x){ if(x < 2) return false; for(int i = 2; i * i <= x; i ++) if(x % i == 0) return false; return true; } i32 main() { ios::sync_with_stdio(false), cin.tie(nullptr); int n; cin >> n; for(int i = 1; n > 0; i += 10) if(isPrime(i)) cout << i << " ", n --; return 0; }

7|0占位


https://atcoder.jp/contests/nikkei2019-2-qual/tasks/nikkei2019_2_qual_d

8|0204D


https://atcoder.jp/contests/abc204/tasks/abc204_d

m=Ti

设 dp 的状态转移方程为 f[i][j] 表示前 i 个菜肴,占用第一个烤箱时长为 j 的方案是否存在。

这样的话直接 O(nm) 的枚举转移就好了

#include <bits/stdc++.h> using namespace std; int main(){ int N; cin >> N; vector<int> T(N); for (int i = 0; i < N; i++){ cin >> T[i]; } int S = 0; for (int i = 0; i < N; i++){ S += T[i]; } vector<vector<bool>> dp(N + 1, vector<bool>(S + 1, false)); dp[0][0] = true; for (int i = 0; i < N; i++){ for (int j = 0; j <= S; j++){ if (dp[i][j]){ dp[i + 1][j] = true; dp[i + 1][j + T[i]] = true; } } } int ans = S; for (int i = 0; i <= S; i++){ if (dp[N][i]){ ans = min(ans, max(i, S - i)); } } cout << ans << endl; }

但是我们注意到对于任意一个 j 满足 f[i][j] 成立,则一定会有 f[i+1][j+Ti+1] 成立。如果吧 f[i] 看成一个二进制数,则相当于左移 Ti+1 位,这样的话,我们可以是用 bitset 快速求解。

#include <bits/stdc++.h> using namespace std; using i32 = int32_t; const int M = 1e5; bitset<M + 1> f; i32 main() { ios::sync_with_stdio(false), cin.tie(nullptr); int n, m = 0; cin >> n; f[0] = true; for (int i = 1, t; i <= n; i++) { cin >> t; m += t; f |= (f << t); } int res = INT_MAX; for (int i = 0; i <= m; i++) if (f[i]) res = min(res, max(i, m - i)); cout << res; return 0; }

9|0207E


https://atcoder.jp/contests/abc207/tasks/abc207_e

f[i][j] 表示分成 i 段的情况下前 j 个元素的方案数。可以得到一个 O(n3) 的转移如下

f[i][j]=k=0j1f[i1][k]×(sum(k+1,j)0modi)

然后 sum(k+1,j)0modi 等价与 (1,j)(1,k)modi

因此我们其实可以用 i 个前缀和计算出转移

#include <bits/stdc++.h> using namespace std; using i32 = int32_t; using i64 = long long; #define int i64 using vi = vector<int>; const int mod = 1e9 + 7; i32 main() { ios::sync_with_stdio(false), cin.tie(nullptr); int n; cin >> n; vi a(n + 1); for (int i = 1; i <= n; i++) cin >> a[i], a[i] += a[i - 1]; vector f(n + 1, vi(n + 1)); f[0][0] = 1; for (int i = 1; i <= n; i++) { vector<int> cnt(i); cnt[0] = f[i - 1][0]; for (int j = 1; j <= n; j++) f[i][j] = cnt[a[j] % i], (cnt[a[j] % i] += f[i - 1][j]) %= mod; } int res = 0; for (int i = 1; i <= n; i++) res = (res + f[i][n]) % mod; cout << res; return 0; }

10|0074C


https://atcoder.jp/contests/abc074/tasks/arc083_a

一个满有意思的枚举题。看似是 dp 但实际上估计范围后,发现枚举复杂度是正确的。

#include<bits/stdc++.h> using namespace std; using i32 = int32_t; using i64 = long long; #define int i64 const int inf = INT_MAX / 2; i32 main() { int A, B, C, D, E, F; cin >> A >> B >> C >> D >> E >> F; A *= 100, B *= 100; int x = 1, y = 0; for (int i = 0; A * i <= F; i++) for (int j = 0; A * i + B * j <= F; j++) { int p = A * i + B * j, q = 0; if (p == 0) continue; int T = min(F - p, p * E / 100); for (int k = 0, l; k * C <= T; k++) { l = (T - k * C) / D; q = max(q, k * C + l * D); } if (x + y != 0 and q * x > y * (p + q)) x = p + q, y = q; } if (y == 0) cout << A << " 0"; else cout << x << " " << y; return 0; }

__EOF__

本文作者PHarr
本文链接https://www.cnblogs.com/PHarr/p/18375766.html
关于博主:前OIer,SMUer
版权声明CC BY-NC 4.0
声援博主:如果这篇文章对您有帮助,不妨给我点个赞
posted @   PHarr  阅读(72)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 单元测试从入门到精通
· 上周热点回顾(3.3-3.9)
· winform 绘制太阳,地球,月球 运作规律
历史上的今天:
2023-08-23 2-SAT
2023-08-23 AtCoder Beginner Contest 315
点击右上角即可分享
微信分享提示