Codeforces Round #888 (Div. 3) A-G
A
代码
#include <bits/stdc++.h> using namespace std; using ll = long long; bool solve() { int n, m, k, H; cin >> n >> m >> k >> H; int cnt = 0; for (int i = 1;i <= n;i++) { int h; cin >> h; if (abs(h - H) % k) continue; int d = abs(h - H) / k; cnt += 1 <= d && d <= m - 1; } cout << cnt << '\n'; return true; } int main() { std::ios::sync_with_stdio(0), cin.tie(0), cout.tie(0); int t = 1; cin >> t; while (t--) { if (!solve()) cout << -1 << '\n'; } return 0; }
B
代码
#include <bits/stdc++.h> using namespace std; using ll = long long; int a[200007]; bool odd[200007]; bool solve() { int n; cin >> n; for (int i = 1;i <= n;i++) cin >> a[i], odd[i] = a[i] & 1; sort(a + 1, a + n + 1); for (int i = 1;i <= n;i++) if ((a[i] & 1) != odd[i]) return false; cout << "YES" << '\n'; return true; } int main() { std::ios::sync_with_stdio(0), cin.tie(0), cout.tie(0); int t = 1; cin >> t; while (t--) { if (!solve()) cout << "NO" << '\n'; } return 0; }
C
代码
#include <bits/stdc++.h> using namespace std; using ll = long long; int c[200007]; bool solve() { int n, k; cin >> n >> k; for (int i = 1;i <= n;i++) cin >> c[i]; int l = 1, cntl = 0; while (cntl < k) { if (l > n) return false; cntl += c[1] == c[l]; l++; } l--; int r = n, cntr = 0; while (cntr < k) { if (r < 1) return false; cntr += c[n] == c[r]; r--; } r++; if (l < r || c[1] == c[n]) cout << "YES" << '\n'; else return false; return true; } int main() { std::ios::sync_with_stdio(0), cin.tie(0), cout.tie(0); int t = 1; cin >> t; while (t--) { if (!solve()) cout << "NO" << '\n'; } return 0; }
D
题意
给定长度为 的前缀和 ,问原数组是否可能为一个长度为 的排列。
题解
知识点:枚举。
考虑还原各个元素,即 ,记为数组 。
我们记还原过程中重复的数字,或超出范围排列的数字为坏数。
我们发现,如果前缀和是由排列得到的,那么至多有一个坏数,我们分类讨论:
- 若没有坏数,那么一定可能。
- 若有一个坏数,则枚举排列中还未出现的数字,当且仅当未出现的数字的和等于坏数才可能。
- 若有多个坏数,则一定不可能。
时间复杂度
空间复杂度
代码
#include <bits/stdc++.h> using namespace std; using ll = long long; ll a[200007]; bool vis[200007]; bool solve() { int n; cin >> n; for (int i = 1;i <= n;i++) vis[i] = 0; for (int i = 1;i <= n - 1;i++) cin >> a[i]; ll bad = 0; for (int i = 1;i <= n - 1;i++) { ll d = a[i] - a[i - 1]; if (d > n || vis[d]) { if (bad) return false; bad = d; } else vis[d] = 1; } if (bad) { int sum = 0; for (int i = 1;i <= n;i++) { if (!vis[i]) sum += i; } if (sum != bad) return false; } cout << "YES" << '\n'; return true; } int main() { std::ios::sync_with_stdio(0), cin.tie(0), cout.tie(0); int t = 1; cin >> t; while (t--) { if (!solve()) cout << "NO" << '\n'; } return 0; }
E
题意
有 种药以及它们的价格 ,现在其中 种免费供应,并给出每种药的合成配方(有些药只能买),每次合成需要的药会消耗掉之后要用就要再买,保证配方自己不能合成自己(直接或者间接都不能)。
问合成每种药的最少花费是多少。
题解
知识点:DAG上dp。
把关系建图,在dag上dp即可。
时间复杂度
空间复杂度
代码
#include <bits/stdc++.h> using namespace std; using ll = long long; vector<int> g[200007]; int c[200007]; int f[200007]; void dfs(int u) { if (f[u] != -1) return; f[u] = c[u]; ll sum = 0; for (auto v : g[u]) { dfs(v); sum += f[v]; } if (g[u].size()) f[u] = min(sum, (ll)f[u]); } bool solve() { int n, k; cin >> n >> k; for (int i = 1;i <= n;i++) cin >> c[i], f[i] = -1, g[i].clear(); for (int i = 1;i <= k;i++) { int x; cin >> x; c[x] = 0; } for (int i = 1;i <= n;i++) { int m; cin >> m; for (int j = 1;j <= m;j++) { int v; cin >> v; g[i].push_back(v); } } for (int i = 1;i <= n;i++) if (f[i] == -1) dfs(i); for (int i = 1;i <= n;i++) cout << f[i] << " \n"[i == n]; return true; } int main() { std::ios::sync_with_stdio(0), cin.tie(0), cout.tie(0); int t = 1; cin >> t; while (t--) { if (!solve()) cout << -1 << '\n'; } return 0; }
F
题意
给定 和 个数字的数组 。
找到两个不同的位置 和数字 使得 最大。
题解
方法一
知识点:位运算,贪心。
考虑 异或 对最后答案的影响。我们发现,对于某一位,只要 是同 ,那么 只需要 ;如果是同 ,那么 只需要 ,其他情况无论 这一位是什么最后结果都是 。
那么问题变为,找到同或 最大的即可(也可以是异或最小,两者是对称的)。
这里需要一个结论:一组数字同或最大(异或最小)的一对,一定出现在大小相邻的两个数字中。
感性的理解就是,大小相邻数字同 且出现在高位的情况比与其他数字匹配的情况多。
因此我们对数字从小到大排序,枚举每一对即可。
时间复杂度
空间复杂度
方法二
知识点:位运算,Trie,贪心。
如果得不到上面的结论,我们可以考虑类似最大异或和问题一样用Trie解决。
显然,对于同或最大,从高位到低位贪心地保证数字相同即可,和异或最大刚好相反。
时间复杂度
空间复杂度
代码
方法一
#include <bits/stdc++.h> using namespace std; using ll = long long; pair<int, int> a[200007]; bool solve() { int n, k; cin >> n >> k; for (int i = 1;i <= n;i++) cin >> a[i].first, a[i].second = i; sort(a + 1, a + n + 1); int mx = -1; array<int, 3> ans{}; for (int i = 2;i <= n;i++) { int res = ~(a[i - 1].first ^ a[i].first) & ((1 << k) - 1); if (res > mx) { mx = res; ans[0] = a[i - 1].second; ans[1] = a[i].second; ans[2] = (~a[i - 1].first & ~a[i].first) & ((1 << k) - 1); } } cout << ans[0] << ' ' << ans[1] << ' ' << ans[2] << '\n'; return true; } int main() { std::ios::sync_with_stdio(0), cin.tie(0), cout.tie(0); int t = 1; cin >> t; while (t--) { if (!solve()) cout << -1 << '\n'; } return 0; }
方法二
#include <bits/stdc++.h> using namespace std; using ll = long long; class Trie { struct Node { array<int, 2> nxt; int val; int id; }; int bit; vector<Node> node; Node NIL; public: Trie(int _bit = 0) { init(_bit); } void init(int _bit) { bit = _bit; NIL = { {0,0},0 }; node.assign(1, NIL); } void insert(int s, int id) { int p = 0; for (int i = bit - 1;i >= 0;i--) { int c = (s >> i) & 1; if (!node[p].nxt[c]) { node[p].nxt[c] = node.size(); node.push_back(NIL); } p = node[p].nxt[c]; } node[p].val = s; node[p].id = id; } pair<int, int> find_max(int s) { int p = 0; for (int i = bit - 1;i >= 0;i--) { int c = (s >> i) & 1; if (node[p].nxt[c]) p = node[p].nxt[c]; else p = node[p].nxt[c ^ 1]; } return { node[p].val,node[p].id }; } }; bool solve() { int n, k; cin >> n >> k; Trie trie(k); int mx = -1; array<int, 3> ans{}; for (int i = 1, x;i <= n;i++) { cin >> x; if (i == 1) { trie.insert(x, i); continue; } auto [y, id] = trie.find_max(x); int res = ~(x ^ y) & ((1 << k) - 1); if (res > mx) { mx = res; ans[0] = i; ans[1] = id; ans[2] = (~x & ~y) & ((1 << k) - 1); } trie.insert(x, i); } cout << ans[0] << ' ' << ans[1] << ' ' << ans[2] << '\n'; return true; } int main() { std::ios::sync_with_stdio(0), cin.tie(0), cout.tie(0); int t = 1; cin >> t; while (t--) { if (!solve()) cout << -1 << '\n'; } return 0; }
G
题意
有 个点,每个点的高度为 ,有 条双向路径。
若 存在路径,则从 到 需要的体力是 (负数则恢复体力),若没有足够的体力就不能走。
有 个询问,每个询问包含起点 ,终点 ,以及初始体力 ,问能否从起点到终点。
题解
知识点:并查集,离线。
我们发现从 到 所需的体力一定为 。因此从 能不能走到 ,取决于是否存在于一条路径使得,路径上的所有点的高度不超过 。
对于边 ,当且仅当能到达的高度大于等于两个端点时,这条边才有用,因此设边权为 。
此时,我们可以最小生成树+倍增在线求路径最大值(典中典),也可以离线+启发式合并+DSU处理询问,但这里采用离线+排序+DSU求,比较容易。
考虑对 从小到大排序,对边权从小到大排序,那么每次询问先将边权不超过 的边加入dsu再询问即可。
时间复杂度
空间复杂度
代码
#include <bits/stdc++.h> using namespace std; using ll = long long; struct DSU { vector<int> fa; DSU(int n = 0) { init(n); } void init(int n) { fa.assign(n + 1, 0); iota(fa.begin(), fa.end(), 0); } int find(int x) { return fa[x] == x ? x : fa[x] = find(fa[x]); } bool same(int x, int y) { return find(x) == find(y); } void merge(int x, int y) { fa[find(x)] = find(y); } }; int h[200007]; tuple<int, int, int> e[200007]; tuple<int, int, int, int> Q[200007]; bool ans[200007]; bool solve() { int n, m; cin >> n >> m; for (int i = 1;i <= n;i++) cin >> h[i]; for (int i = 1;i <= m;i++) { int u, v; cin >> u >> v; e[i] = { u,v, max(h[u],h[v]) }; } sort(e + 1, e + m + 1, [&](auto a, auto b) {return get<2>(a) < get<2>(b);}); int q; cin >> q; for (int i = 1;i <= q;i++) { int u, v, w; cin >> u >> v >> w; Q[i] = { u,v,w + h[u], i }; } sort(Q + 1, Q + q + 1, [&](auto a, auto b) {return get<2>(a) < get<2>(b);}); int pos = 1; DSU dsu(n); for (int i = 1;i <= q;i++) { while (pos <= m && get<2>(e[pos]) <= get<2>(Q[i])) { dsu.merge(get<0>(e[pos]), get<1>(e[pos])); pos++; } ans[get<3>(Q[i])] = dsu.same(get<0>(Q[i]), get<1>(Q[i])); } for (int i = 1;i <= q;i++) cout << (ans[i] ? "YES" : "NO") << '\n'; cout << '\n'; return true; } int main() { std::ios::sync_with_stdio(0), cin.tie(0), cout.tie(0); int t = 1; cin >> t; while (t--) { if (!solve()) cout << -1 << '\n'; } return 0; }
本文来自博客园,作者:空白菌,转载请注明原文链接:https://www.cnblogs.com/BlankYang/p/17583899.html
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧