2023牛客寒假算法基础集训营3 A-I+K
A
题解
知识点:贪心。
把所有正偶数除成奇数,即可。
(人傻了没加 WA2
时间复杂度
空间复杂度
代码
#include <bits/stdc++.h> using ll = long long; using namespace std; int main() { std::ios::sync_with_stdio(0), cin.tie(0), cout.tie(0); int n; cin >> n; ll ans = 0; for (int i = 1;i <= n;i++) { int x; cin >> x; while (x > 0 && x % 2 == 0) x /= 2; ans += x; } cout << ans << '\n'; return 0; }
B
题解
知识点:数学,构造。
特判 无解。
可以先放边长为 正方形,随后边长每增加 需要最少 块,直到边长为 后,边长每增加 需要最少 块。以此类推,当边长为 时,边长每增加 需要 块积木。
显然,摆完第一轮边长为 后,剩下的 个积木,而 ,因此不可能摆到需要 个积木的情况。
综上,边长最大值为 。
本题也可以用二分边长做。
(没考虑 大小,傻了吧唧的算了通式,不过可以出题了qwq
考虑 块积木,给定 ,每块积木大小为 ,求能摆成正方形的边长最大值。
时间复杂度
空间复杂度
代码
#include <bits/stdc++.h> using ll = long long; using namespace std; bool solve() { ll n; cin >> n; if (n == 2) return false; ll a = (n + 1) / 2; ll ans = a + (n - a) / 3; cout << ans << '\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; }
C
题解
知识点:构造。
或 时无解。
考虑 的情况。
- 时显然形如构造 的循环即可。
- 时,前 项构造成 ,其余仿照 情况。
- 时,前 项构造成 ,其余仿照 情况。
- 时,前 项构造分为 项和 项两组仿照 情况,其余仿照 情况。
时间复杂度
空间复杂度
代码
#include <bits/stdc++.h> using ll = long long; using namespace std; bool solve() { int n; cin >> n; if (n <= 3 || n == 7) return false; int m = 1; if (n % 4 == 1) { cout << "4 5 1 2 3" << ' '; m = 6; } else if (n % 4 == 2) { cout << "4 5 6 1 2 3" << ' '; m = 7; } else if (n % 4 == 3) { cout << "4 5 1 2 3 9 10 11 6 7 8" << ' '; m = 12; } for (int i = m;i <= n;i += 4) { cout << i + 2 << ' ' << i + 3 << ' ' << i << ' ' << i + 1 << ' '; } 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; }
D
题解
知识点:博弈论。
这类题需要先对局面分类,每种局面考虑找到一组平衡的操作,即对于其中一人,无论另一人如何操作,他都可以在下一次操作后回到原来的局面。
考虑将 分奇偶情况:
- 为偶数,小红每次可以选 ,随后数变为奇数局面,小紫只有奇数因子能选,数又变为偶数局面。到最后,必然是小紫让数变为 ,因为只有小紫能让数变为偶数。因此,偶数局面小红必胜。
- 为奇数,根据 为偶数的推理,发现奇数局面小红必败。
时间复杂度
空间复杂度
代码
#include <bits/stdc++.h> using ll = long long; using namespace std; int main() { std::ios::sync_with_stdio(0), cin.tie(0), cout.tie(0); ll n; cin >> n; if (n & 1) cout << "yukari" << '\n'; else cout << "kou" << '\n'; return 0; }
E
题解
知识点:计算几何。
设 构成等腰直角三角形,其中 为顶点且在 右侧,满足方程:
方程可以通过全等三角形证明。
显然 和 关于 的对称点同时是或不是整数点,解出 后判断是否为整数即可。
(平面几何永远的痛,并且以为无解输出-1收获WA
时间复杂度
空间复杂度
代码
#include <bits/stdc++.h> using ll = long long; using namespace std; int main() { std::ios::sync_with_stdio(0), cin.tie(0), cout.tie(0); ll a, b, c, d; cin >> a >> b >> c >> d; ll x = a + d + c - b; ll y = a + d + b - c; if (x & 1 || y & 1)cout << "No Answer!" << '\n'; else cout << x / 2 << ' ' << y / 2 << '\n'; return 0; }
F
题解
知识点:宇宙的终极答案。
通过你高超的中文流读取技术,发现这是营销号特有的文案。
本打算对此嗤之以鼻的你,阅读完样例后逐渐理解了一切,确信 就是宇宙的终极答案。
时间复杂度
空间复杂度
代码
#include <bits/stdc++.h> using ll = long long; using namespace std; int main() { std::ios::sync_with_stdio(0), cin.tie(0), cout.tie(0); cout << 42 << '\n'; return 0; }
G
题解
知识点:模拟,枚举,dfs。
很简单(痛苦)的模拟。
dfs枚举每个 ?
的三种可能即可,注意快速幂前把底数模一下,因为可能炸 long long
。
可以选择预处理数字后边枚举边求值,也可以考虑枚举完再求值。注意,边枚举边求值不太适用于有优先级表达式。
(被表达式求值整了一顿,码力太差了QAQ
时间复杂度
空间复杂度
代码
边枚举边求值
#include <bits/stdc++.h> using namespace std; using ll = long long; ll qpow(ll a, ll k, ll P) { ll ans = 1; while (k) { if (k & 1) ans = ans * a % P; k >>= 1; a = a * a % P; } return ans; } int ans; vector<int> num; vector<char> op(20); bool dfs(int step = 1, ll cur = num[0]) { if (step == num.size()) return cur == ans; op[step] = '+'; if (dfs(step + 1, cur + num[step])) return true; op[step] = '-'; if (dfs(step + 1, cur - num[step])) return true; if (cur > 0 && num[step] > 0) { op[step] = '#'; if (dfs(step + 1, qpow(cur % num[step], cur, num[step]))) return true; } return false; } int main() { std::ios::sync_with_stdio(0), cin.tie(0), cout.tie(0); string s; cin >> s; for (int i = 0;i < s.size();i++) { if (isdigit(s[i])) ans = ans * 10 + s[i] - '0'; else num.push_back(ans), ans = 0; } if (dfs()) { cout << num[0]; for (int i = 1;i < num.size();i++) cout << op[i] << num[i]; cout << '=' << ans << '\n'; } else cout << -1 << '\n'; return 0; }
枚举完求值,用到表达式计算。
这里给了一个模板,可以修改map的优先级,支持带括号的二元运算,以及伪负号运算(指负号运算必须打括号)。
#include <bits/stdc++.h> using ll = long long; using namespace std; ll qpow(ll a, ll k, ll P) { ll ans = 1; while (k) { if (k & 1) ans = ans * a % P; k >>= 1; a = a * a % P; } return ans; } map<char, int> mp = { {'+',0},{'-',0},{'#',0},{'=',0} }; bool calc(string s) { vector<ll> num = { 0 }; vector<char> op; for (auto ch : s) { if (ch >= '0' && ch <= '9') num.back() = num.back() * 10 + ch - '0'; else { while (op.size() && mp[ch] <= mp[op.back()]) { char ope = op.back(); op.pop_back(); ll x = num.back(); num.pop_back(); if (ope == '+') num.back() += x; else if (ope == '-') num.back() -= x; else if (ope == '#') { if (x <= 0) return false; num.back() = qpow(num.back() % x, num.back(), x); } } if (ch == '#' && num.back() <= 0) return false; op.push_back(ch); num.push_back(0); } } return num[0] == num[1]; } string s; bool dfs(int step = 0) { if (step == s.size()) return calc(s); if (s[step] == '?') { s[step] = '+'; if (dfs(step + 1)) return true; s[step] = '-'; if (dfs(step + 1)) return true; s[step] = '#'; if (dfs(step + 1)) return true; s[step] = '?'; } else { while (step < s.size() && s[step] != '?') step++; if (dfs(step)) return true; } return false; } int main() { std::ios::sync_with_stdio(0), cin.tie(0), cout.tie(0); cin >> s; if (dfs()) cout << s << '\n'; else cout << -1 << '\n'; return 0; }
H
题解
知识点:概率dp。
可能重要的前置知识
(大佬请跳过):对于一维期望dp,在第 步时,其向其他状态转移的起点只有 一种,因此在第 步起点是状态 的概率是百分百的,变化的期望直接加就行。
例如,有期望状态 。操作有两种,概率分别为 ,两种操作的贡献分别是 。那么可以有转移方程:
但是,如果在一维期望dp的基础上,设每一步都有多个不同的状态,那么转移时的期望就不是简单加法了。
具体的说,在第 步时,其向其他状态转移的起点如果是 有 种,那么显然这 种状态都有概率成为起点,满足概率的总和为百分百。因此考虑 为起点做转移时,变化的期望需要乘上其作为起点的概率,表示这步操作在 作为起点的概率下的期望。当然,期望 本身不需要再乘一遍概率,因为求这个期望时已经考虑了到这个状态的概率,同时我们还可以知道这 种期望的总和就是第 步的总期望。
例如,有期望状态 ,设 的概率为 。操作有两种,概率分别为 ,我们假设从 都可以通过两种操作转移到 ,两种操作的贡献对于两种状态分别是 和 。那么对于 可以有转移方程:
接下来就可以轻松
(真的吗)做这道题了。
设 为执行到第 步且满足串首状态为 、串尾状态为 的期望个数。其中, 表示串首是 red
或 edr
, 同理。
设 为对应的 发生的概率。注意,除了四种串首尾的状态,还有一种空串的状态,这里没有标记到数组里,但是每步还是得自己手动加上去的,我们记 为空串的概率,空串的期望为 不需要考虑。
因此有转移方程:
写的很详细了,三个 对应三种操作,分别算一下概率和期望转移就行。特别注意, 的操作三转移可以产生十倍加九的期望。
代码用滚动数组压缩了一维, 代表第 步的各种概率/期望, 代表第 步的各种概率/期望。并且,代码转移时是用子状态刷表,而非如上述转移方程填表,因为写起来比较方便。填表也能写,本质都是一样的,很好理解。
推荐使用 Modint
不然开 long long
也救不了打 %
打到手酸。
时间复杂度
空间复杂度
代码
#include <bits/stdc++.h> using ll = long long; using namespace std; const int P = 1e9 + 7; struct Modint { int val; Modint(int _val = 0):val(_val %P) { format(); } Modint(ll _val):val(_val %P) { format(); } //if val in [-P,2P) //maybe slower than global version Modint &format() { if (val < 0) val += P; if (val >= P) val -= P; return *this; } Modint inv()const { return qpow(*this, P - 2); } Modint &operator+=(const Modint &x) { val += x.val;return format(); } Modint &operator-=(const Modint &x) { val -= x.val;return format(); } Modint &operator*=(const Modint &x) { val = 1LL * val * x.val % P;return *this; } Modint &operator/=(const Modint &x) { return *this *= x.inv(); } friend Modint operator-(const Modint &x) { return { -x.val }; } friend Modint operator+(Modint a, const Modint &b) { return a += b; } friend Modint operator-(Modint a, const Modint &b) { return a -= b; } friend Modint operator*(Modint a, const Modint &b) { return a *= b; } friend Modint operator/(Modint a, const Modint &b) { return a /= b; } friend Modint qpow(Modint a, ll k) { Modint ans = 1; while (k) { if (k & 1) ans = ans * a; k >>= 1; a = a * a; } return ans; } friend istream &operator>>(istream &is, Modint &x) { ll _x; is >> _x; x = { _x }; return is; } friend ostream &operator<<(ostream &os, const Modint &x) { return os << x.val; } }; /* f[0/1][0/1][0]:概率 f[0/1][0/1][1]:期望 00 red-red 01 red-edr 10 edr-red 11 edr-edr 注意还有一种空串情况 需要每次操作前手动加 */ int main() { std::ios::sync_with_stdio(0), cin.tie(0), cout.tie(0); int k; cin >> k; array<array<array<Modint, 2>, 2>, 2> f = {}; Modint inv3 = Modint(3).inv(); Modint prob = 1; for (int i = 1;i <= k;i++) { //prod指空串概率,空串期望始终为0不需要管 array<array<array<Modint, 2>, 2>, 2> g = {}; g[0][0][0] = inv3 * prob; g[1][1][0] = inv3 * prob; g[0][0][1] = inv3 * prob; //空串到g[1][1]的期望还是0,不用管 for (auto i : { 0,1 }) { for (auto j : { 0,1 }) { g[i][0][0] += inv3 * f[i][j][0];//操作1后的概率 g[i][1][0] += inv3 * f[i][j][0];//操作2后的概率 g[i][j][0] += inv3 * f[i][j][0];//操作3后的概率 g[i][0][1] += inv3 * f[i][j][1];//操作1后的期望 g[i][0][1] += inv3 * f[i][j][0]; g[i][1][1] += inv3 * f[i][j][1];//操作2后的期望 if (j == 1) g[i][1][1] += inv3 * f[i][j][0];//只有?1才能加 g[i][j][1] += 10 * inv3 * f[i][j][1];//操作3后的期望 if (i == 1 && j == 1) g[i][j][1] += 9 * inv3 * f[i][j][0];//只有11能加9个 } } prob *= inv3; f = g; } Modint sum = 0; for (auto i : { 0,1 })for (auto j : { 0,1 }) sum += f[i][j][1]; cout << sum << '\n'; return 0; }
I
题解
知识点:数论,构造。
-
偶数情况,若 是素数构造 ,则 ; 若 是素数构造 ,则 。
-
奇数情况,因为一定存在 因子,我们考虑使其他因子的和凑出一个偶数 。考虑最简单的素数情况,因为哥德巴赫猜想,一个大于等于 的偶数可以被分解成两个素数之和,我们只要找到两个不同的素数 使得 ,那么构造 ,则 。
注意,哥德巴赫猜想所述是两个素数之和,不是两个不同的素数。经过测试
int
范围内,大于等于 的偶数都可以被分解为两个不同的素数之和,因此大于等于 的奇数我们无脑无解即可。我们需要特判 ,因为这些情况确实有解,但不能通过哥德巴赫猜想构造。
时间复杂度
空间复杂度
代码
#include <bits/stdc++.h> using namespace std; using ll = long long; const int N = 1e6 + 7; bool vis[N]; vector<int> prime; void get_prime(int n) { for (int i = 2;i <= n;i++) { if (!vis[i]) prime.push_back(i); for (int j = 0;j < prime.size() && i * prime[j] <= n;j++) { vis[i * prime[j]] = 1; if (!(i % prime[j])) break; } } } bool solve() { int x; cin >> x; if (x == 1) { cout << 2 << '\n'; return true; } if (x == 3) { cout << 4 << '\n'; return true; } if (x == 7) { cout << 8 << '\n'; return true; } if (x & 1) { for (int i = 0;i < prime.size() && 2 * prime[i] < x - 1;i++) { if (!vis[x - 1 - prime[i]]) { cout << 1LL * prime[i] * (x - 1 - prime[i]) << '\n'; return true; } } } else { if (!vis[x - 1]) cout << 1LL * (x - 1) * (x - 1) << '\n'; else cout << 1LL * 2 * (x - 3) << '\n'; return true; } return false; } int main() { std::ios::sync_with_stdio(0), cin.tie(0), cout.tie(0); int t = 1; cin >> t; get_prime(1e6); while (t--) { if (!solve()) cout << -1 << '\n'; } return 0; }
K
题解
知识点:贪心,数学。
给你前 种素数,每个素数有 个。
设 。现在将这 个素数排成一个序列,设 为序列中 的数的乘积的因子的数量。现在求 的 的和,即 ,的最大值。
显然,我们需要尽可能让前面的 的越大越好。我们知道,乘积的因子数量等于各个素数个数加 的乘积,通过一些尝试很容易发现均摊素数的个数,比连续安排同一种素数得到的结果要大很多,因此我们每次安排还能安排的素数中出现次数最小的那个素数。
我们设 表示出现至少 次的素数个数,我们发现 的结果呈现 。直接求和要加 次是不可行的,因此我们先用差分维护好 ,随后用等比公式对每 个数直接求和。
设 为 段的最后一个数字,那么 段的总和为 ,最后一个数字 ,于是就可以递推求和了。
时间复杂度
空间复杂度
代码
#include <bits/stdc++.h> using ll = long long; using namespace std; const int P = 1e9 + 7; ll qpow(ll a, ll k) { ll ans = 1; while (k) { if (k & 1) ans = ans * a % P; k >>= 1; a = a * a % P; } return ans; } ll inv(ll a) { return qpow(a, P - 2); } int cnt[200007]; 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++) { int x; cin >> x; cnt[1]++; cnt[x + 1]--; } for (int i = 1;i <= 2e5;i++) cnt[i] += cnt[i - 1]; int pre = 1; int ans = 0; for (int i = 1;i <= 2e5;i++) { int f = 1LL * (i + 1) * inv(i) % P; int g = qpow(f, cnt[i]); ans = (ans + 1LL * pre * f % P * (1 - g + P) % P * inv(1 - f + P) % P) % P; pre = 1LL * pre * g % P; } cout << ans << '\n'; return 0; }
本文来自博客园,作者:空白菌,转载请注明原文链接:https://www.cnblogs.com/BlankYang/p/17066469.html
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧