2023牛客暑期多校训练营2 DEFGHIK
D
题解
知识点:贪心。
首先,因为第一个人喜欢吃的可能会被后面的人选中,因此直接选最喜欢吃的可能会浪费机会。所以,我们考虑先看后面的人怎么选,就是倒着贪心,我们考虑证明。
假设当前剩下的菜集合为 ,还剩下 个人没选,这 个人通过我们的策略选的菜的集合是 ,分类讨论:
-
若 ,最优结果与 一致。
-
假设 时的最优结果与 一致。
当 时,设 中除了 之外倒数第 个人最喜欢的菜是 ,对他的选择分类讨论:
- 若选择 ,则结果与 一致。
- 若选择 中除了 和 之外的菜,则后 个人结果不变,倒数第 个人亏。
- 若选择 中的菜,那么后 个人有一个人的选择会改变:
- 若改变的那个人选择了 ,则结果与 一致。
- 若改变的那个人没选择 ,则倒数第 个人亏。
因此 时的最优解与 一致,因此贪心策略是对的。
时间复杂度
空间复杂度
代码
#include <bits/stdc++.h> using namespace std; using ll = long long; int a[2007][2007]; int ans[2007]; bool solve() { int n, m, k; cin >> n >> m >> k; for (int i = 1;i <= n;i++) for (int j = 1;j <= m;j++) cin >> a[i][j]; vector<char> vis(m + 1); for (int i = k;i >= 1;i--) { int p = i % n; if (!p) p = n; int res = 0; for (int j = 1;j <= m;j++) if (!vis[j] && a[p][res] < a[p][j]) res = j; ans[i] = res; vis[res] = 1; } sort(ans + 1, ans + k + 1); for (int i = 1;i <= k;i++) cout << ans[i] << " \n"[i == k]; 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; }
E
题解
知识点:数学。
显然 。注意 ,因此我们先保证 。
我们枚举 ,得到 可能范围的左端点 ,若 ,那么 。
否则,无解。
时间复杂度
空间复杂度
代码
#include <bits/stdc++.h> using namespace std; using ll = long long; ll sqrt_fix(ll x, bool mode = false) { ll v = sqrt(x); while (v * v < x) ++v; while (v * v > x) --v; if (mode && v * v < x) v++; return v; } bool solve() { ll x; cin >> x; for (__int128_t i = x, j = x + 1;i <= (ll)1e18;i *= 10, j *= 10) { ll l = sqrt_fix(i, 1); if (l * l <= min(j - 1, (__int128_t)1e18)) { cout << l << '\n'; return true; } } return false; } 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
题解
知识点:博弈论,二分图。
一个至关重要的模型,二分图博弈:
给出一张二分图和起点,轮流进行操作,每次操作只能选择与上一个被选的点相邻的点,且不能选择已经选择过的点,无法选点的人输掉。
关于上面的模型,结论是:若起始点一定在最大匹配上,那么先手赢,否则后手赢。
那和这道题有什么关系呢?
这道题我们把三元组 抽象成三维坐标的一个点,那么所有状态构成一个 正方体,每个点可以走到相邻的点,那么这道题就变成了一个二分图博弈了,不过图是在三维上建的。接下来分类讨论:
-
当 为偶数时,显然每个点都可以匹配到,因此所有点一定在最大匹配上,所以先手必胜。
-
当 为奇数时,显然会有一个点在点较多一部中且没被匹配到,我们将证明这个点可以是较多一部中的任意一个点。
首先,若点 在较多一部中,那么 是一个奇数。若 是偶数,那么一定可以走一步,就可以与它匹配。
因此, 中一定有至少一个奇数,我们不妨假设 是奇数,显然 时的点都可以匹配到,所以我们可以只考虑 这一层的情况。
因为 一定是个偶数,我们发现,总能构造出一种方法使得 不在其中。
综上, 为奇数时,后手必胜,否则先手必胜。
时间复杂度
空间复杂度
代码
#include <bits/stdc++.h> using namespace std; using ll = long long; bool solve() { int n, r, g, b; cin >> n >> r >> g >> b; if ((n & 1) && (r + g + b & 1)) cout << "Bob" << '\n'; else cout << "Alice" << '\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
题解
知识点:manacher。
中心对称字符串和回文串可以理解为是同一个东西,不过匹配方式需要自定义。
这里需要用manacher算法,其中的细节是需要修改的。
接下来是关于本题的最关键的一个性质:若一个字符串能被分为若干个中心对称串,那么它一定能分为若干个任意前缀不是中心对称的中心对称串。
这个结论让我们在从左到右枚举对称中心的途中,对于一个好的前缀,可以往后截断一个最短的前缀中心对称串,而不影响答案。
若最后整个字符串能被截断完了,那么就是好的,否则不好。
证明:
假设一个中心对称串 ,其中 是最短的前缀对称串。
我们证明 。若 ,那么 的对称串 ,有 ,使得 。 那么,其中 一定是中心对称串,而 在 内,因此 的前缀一定有 的对称串也是中心对称的,因此 就不是最短的。
现在对于一个中心对称串 一定可以表达为 ,其中 都是中心对称串,即一个中心对称串若含有更短的前缀中心对称串,那么就可以继续分下去,直到不包含任意前缀中心对称串。
这个结论同样适用于任何具有回文性质的字符串划分,都可以依次截断最短的前缀回文。
时间复杂度
空间复杂度
代码
#include <bits/stdc++.h> using namespace std; using ll = long long; char rev[256]; void init() { rev['o'] = 'o'; rev['s'] = 's'; rev['x'] = 'x'; rev['z'] = 'z'; rev['b'] = 'q'; rev['q'] = 'b'; rev['d'] = 'p'; rev['p'] = 'd'; rev['n'] = 'u'; rev['u'] = 'n'; rev['|'] = '|'; } bool check(char a, char b) { return b == rev[a]; } bool manachar(const string &_s) { string s = "$|"; for (int i = 1;i < _s.size();i++) { s += _s[i]; s += "|"; } s += "&"; int n = s.size() - 1; vector<int> d(n + 1); int start = 1; int p = 0, r = 0; for (int i = 1;i <= n - 1;i++) { if (i <= r) d[i] = min(r - i + 1, d[2 * p - i]); else d[i] = 0; while (check(s[i - d[i]], s[i + d[i]])) d[i]++; if (i + d[i] - 1 > r) { p = i; r = i + d[i] - 1; } if (i - d[i] + 1 <= start) { start = i + d[i]; s[start - 1] = '$'; i = start - 1; } } return start == n; } bool solve() { string s; cin >> s; s = "?" + s; cout << (manachar(s) ? "Yes" : "No") << '\n'; return true; } int main() { std::ios::sync_with_stdio(0), cin.tie(0), cout.tie(0); int t = 1; cin >> t; init(); while (t--) { if (!solve()) cout << -1 << '\n'; } return 0; }
H
题解
知识点:位运算,前缀和。
考虑快速求出操作区间对初始数字 的影响。
这里一个重要的转换是,操作A对 取反等价于 ,因此考虑将答案表达为 的形式。
显然,只有操作A能改变 ,因此 即为操作区间 中操作A的个数。
因为操作A能改变 的正负,所以任何操作对 的影响,与之后经历操作A的次数有关,我们分类讨论:
- 对于操作B,若其后有偶数个操作A,那么贡献为 ,否则为 。
- 对于操作A,若其后有偶数个操作A,那么贡献为 ,否则为 。
我们发现这个过程是能用前缀和维护的。具体来说,我们分别记录 在前缀 操作下的结果。
现在我们询问 区间的结果。显然, 。对于 我们有 ,因为对于 的操作中,我们需要消除 对 的影响,因为这一段操作被之后 的操作A影响过了,因此我们需要对 修正,即 。
最终结果为 。
当然这个过程还能通过初等矩阵维护。
时间复杂度
空间复杂度
代码
#include <bits/stdc++.h> using namespace std; using ll = long long; int k[200007]; int b[200007]; int main() { std::ios::sync_with_stdio(0), cin.tie(0), cout.tie(0); int n, q; cin >> n >> q; string s; cin >> s; s = "?" + s; k[0] = 1; for (int i = 1;i <= n;i++) { if (s[i] == 'A') { k[i] = -k[i - 1]; b[i] = -b[i - 1] - 1; } else { k[i] = k[i - 1]; b[i] = b[i - 1] + 1; } } ll ans = 0; while (q--) { int l, r; cin >> l >> r; l = (ans ^ l) % n + 1; r = (ans ^ r) % n + 1; if (l > r) swap(l, r); string t; cin >> t; ll x = 0, P = 1LL << t.size(); for (auto ch : t) (x <<= 1) |= ch == '1'; x -= b[l - 1]; x *= k[l - 1]; x *= k[r]; x += b[r]; ((x %= P) += P) %= P; ans = x; for (int i = t.size() - 1;i >= 0;i--) cout << ((x >> i) & 1); cout << '\n'; } return 0; }
I
题解
知识点:构造。
构造形如下方,注意字符顺序要严格:
xoxoxo oxoxox oxoxox xoxoxo ......
当且仅当 且 是奇数时,这种构造需要将 ox
反转,变为:
oxoxo xoxox xoxox
这是为了保证数量的严格。
可以证明 有一个为偶数时,这种构造一定是合法的。
当满足 且 是奇数时,发现原来的构造会导致 o
比 x
多一个。
时间复杂度
空间复杂度
代码
#include <bits/stdc++.h> using namespace std; using ll = long long; bool solve() { int n, m; cin >> n >> m; bool f = (n / 2 & 1) && (n & 1) && (m & 1); for (int i = 1;i <= n;i++) { for (int j = 1;j <= m;j++) { if (j & 1) cout << ((i / 2 & 1) ^ f ? "o" : "x"); else cout << ((i / 2 & 1) ^ f ? "x" : "o"); } 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; }
K
题解
知识点:线性dp。
若没有向右移动,这道dp只需要多记录上一个位置有没有盖子即可。
但这道题可以向右移动,因此再多记录一维上一个位置若原来有盖子,有没有向右移动。
还有其他的状态方案,具体可以看代码。
我用的是方法一,上面讲的是方法二的,我觉得方法二更通用。
时间复杂度
空间复杂度
代码
方法一
#include <bits/stdc++.h> using namespace std; using ll = long long; int a[1000007]; bool b[1000007]; ll f[1000007][4]; // 第i个位置,没有/前面/自己/后面拿盖子 int main() { std::ios::sync_with_stdio(0), cin.tie(0), cout.tie(0); int n; cin >> n; for (int i = 1;i <= n;i++) cin >> a[i]; for (int i = 1;i <= n;i++) cin >> b[i]; for (int i = 1;i <= n;i++) { f[i][0] = max({ f[i - 1][0],f[i - 1][1],f[i - 1][2],f[i - 1][3] }); if (b[i - 1]) { f[i][1] = max({ f[i - 1][1], f[i - 2][0],f[i - 2][1],f[i - 2][2] }) + a[i]; } if (b[i]) { f[i][2] = max({ f[i - 1][0],f[i - 1][1],f[i - 1][2] }) + a[i]; } if (b[i + 1]) { f[i][3] = max({ f[i - 1][0],f[i - 1][1],f[i - 1][2],f[i - 1][3] }) + a[i]; } } cout << max({ f[n][0],f[n][1],f[n][2] }) << '\n'; return 0; }
方法二
#include <bits/stdc++.h> using namespace std; using ll = long long; const int inf = 1e9; int a[1000007]; bool b[1000007]; ll f[1000007][2][2]; // 前i个位置,有无盖子,有无右移(btw:只有左移操作那只需要有无盖子) int main() { std::ios::sync_with_stdio(0), cin.tie(0), cout.tie(0); int n; cin >> n; for (int i = 1;i <= n;i++) cin >> a[i]; for (int i = 1;i <= n;i++) cin >> b[i]; f[0][0][0] = 0; f[0][0][1] = -inf; f[0][1][0] = -inf; f[0][1][1] = -inf; for (int i = 1;i <= n;i++) { if (b[i]) { f[i][0][0] = f[i - 1][0][0] + a[i - 1]; f[i][0][1] = max(f[i - 1][0][0], f[i - 1][1][0]); f[i][1][0] = max(f[i - 1][0][0], f[i - 1][1][0]) + a[i]; f[i][1][1] = max(f[i - 1][0][1], f[i - 1][1][1]) + a[i]; } else { f[i][0][0] = max({ f[i - 1][0][0],f[i - 1][1][0] }); f[i][0][1] = -inf; f[i][1][0] = max({ f[i - 1][0][1],f[i - 1][1][1] }) + a[i]; f[i][1][1] = -inf; } } cout << max({ f[n][0][0],f[n][1][0] }) << '\n'; return 0; }
方法三
#include <bits/stdc++.h> using namespace std; using ll = long long; int a[1000007]; bool b[1000007]; ll f[1000007][3]; // 第i个位置,有给前面,没有不管/有自己用,没有从前面拿/一定有且给后面 int main() { std::ios::sync_with_stdio(0), cin.tie(0), cout.tie(0); int n; cin >> n; for (int i = 1;i <= n;i++) cin >> a[i]; for (int i = 1;i <= n;i++) cin >> b[i]; for (int i = 1;i <= n;i++) { if (b[i]) { f[i][0] = f[i - 1][0] + a[i - 1]; f[i][1] = max(f[i - 1][0], f[i - 1][1]) + a[i]; f[i][2] = max({ f[i - 1][0], f[i - 1][1], f[i - 1][2] }) + a[i + 1]; } else { f[i][0] = max(f[i - 1][0], f[i - 1][1]); f[i][1] = f[i - 1][2]; } } cout << max(f[n][0], f[n][1]) << '\n'; return 0; }
本文来自博客园,作者:空白菌,转载请注明原文链接:https://www.cnblogs.com/BlankYang/p/17580860.html
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧