2024 暑假友谊赛 1
2024 暑假友谊赛 1
A - 😜
题意
现有 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 - 😭
题意
给你 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 - 😠
题意
给你 \(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 - 😓
题意
给你 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 - 🐔
题意
给你一棵树, 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 - 🐂
题意
你有一个 \(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 - 😄
题意
给你两个数 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 -
题意
一家水果店出售苹果。您可以按照任意顺序多次执行以下操作:
- 用 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 - 😚
题意
给定一个序列 𝑎,问将其划分成若干段,满足第 𝑖 段的和是 𝑖 的倍数的划分方案的个数。
思路
考虑 dp。
设 \(dp_{i,j}\) 表示前 i 个数分成 j 段的方案数,可以得到以下转移方程:
复杂度 \(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; }
本文作者:Ke_scholar
本文链接:https://www.cnblogs.com/Kescholar/p/18300823
版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
2023-07-13 SMU Summer 2023 Contest Round 3