2023牛客寒假算法基础集训营6 A-L
A
题解
知识点:模拟。
如题。
代码
#include <bits/stdc++.h> using namespace std; using ll = long long; int main() { std::ios::sync_with_stdio(0), cin.tie(0), cout.tie(0); int x; cin >> x; if (x <= 7) cout << "very easy" << '\n'; else if (x <= 233) cout << "easy" << '\n'; else if (x <= 10032) cout << "medium" << '\n'; else if (x <= 114514) cout << "hard" << '\n'; else if (x <= 1919810) cout << "very hard" << '\n'; else cout << "can not imagine" << '\n'; return 0; }
B
题解
知识点:正因数集合,枚举,二分。
预处理 所有数的因数,设 为因数 出现的位置,对每个数处理即可。
查询 时,只需在 内二分查找大于 的位置,然后就可以得到 作为因数出现的位置个数。
时间复杂度
空间复杂度
代码
#include <bits/stdc++.h> using namespace std; using ll = long long; int a[400007]; vector<int> pos[200007]; vector<int> factor[200007]; void get_factor(int n) { for (int i = 1;i <= n;i++) for (int j = i;j <= n;j += i) factor[j].push_back(i); } int main() { std::ios::sync_with_stdio(0), cin.tie(0), cout.tie(0); get_factor(2e5); int n, q; cin >> n >> q; for (int i = 1;i <= n;i++) cin >> a[i]; for (int i = 1;i <= n;i++) for (auto f : factor[a[i]]) pos[f].push_back(i); while (q--) { int op, x; cin >> op >> x; if (op == 1) { a[++n] = x; for (auto f : factor[a[n]]) pos[f].push_back(n); } else { int id = lower_bound(pos[a[x]].begin(), pos[a[x]].end(), x) - pos[a[x]].begin(); cout << pos[a[x]].size() - id - 1 << '\n'; } } return 0; }
C
题解
知识点:贪心。
手动模拟一下发现系数是二项式系数,大的数放中间小的放外边即可。
时间复杂度
空间复杂度
代码
#include <bits/stdc++.h> using namespace std; using ll = long long; const int mod = 1e9 + 7; int a[1007]; int s[1007]; int main() { std::ios::sync_with_stdio(0), cin.tie(0), cout.tie(0); int n; cin >> n; for (int i = 1;i <= (n + 1) / 2;i++) a[i] = 2 * i - 1; for (int i = 1;i <= n / 2;i++) a[n - i + 1] = 2 * i; for (int i = 1;i <= n;i++) s[i] = a[i]; for (int i = 1;i <= n - 1;i++) { for (int j = 1;j <= n - i;j++) { (s[j] += s[j + 1]) %= mod; } } cout << s[1] << '\n'; for (int i = 1;i <= n;i++) cout << a[i] << " \n"[i == n]; return 0; }
D
题解
知识点:字符串,线性dp。
为了容易求出去掉一个字母后的子序列个数,考虑分别dp前缀和后缀子序列 t = "udu"
个数。
设 为 中子序列 的个数, 为 中子序列 的个数,特别地 时为空串。转移方程很显然,详见代码。
最后枚举 个位置,第 个位置删去后子序列总数为 取最小值的位置即可。
时间复杂度
空间复杂度
代码
#include <bits/stdc++.h> using namespace std; using ll = long long; ll f[200007][4]; ll g[200007][4]; int main() { std::ios::sync_with_stdio(0), cin.tie(0), cout.tie(0); string s; cin >> s; int n = s.size(); s = "?" + s; string t = "?udu"; f[0][0] = 1; for (int i = 1;i <= n;i++) { for (int j = 0;j <= 3;j++) { f[i][j] = f[i - 1][j]; if (s[i] == t[j]) f[i][j] += f[i - 1][j - 1]; } } reverse(s.begin() + 1, s.end()); reverse(t.begin() + 1, t.end()); g[0][0] = 1; for (int i = 1;i <= n;i++) { for (int j = 0;j <= 3;j++) { g[i][j] = g[i - 1][j]; if (s[i] == t[j]) g[i][j] += g[i - 1][j - 1]; } } reverse(s.begin() + 1, s.end()); reverse(t.begin() + 1, t.end()); int pos = -1; ll mn = 1e18; for (int i = 1;i <= n;i++) { ll tmp = 0; for (int j = 0;j <= 3;j++) tmp += f[i - 1][j] * g[n - i][3 - j]; if (tmp < mn) mn = tmp, pos = i; } s[pos] = 'a'; cout << s.substr(1, n) << '\n'; return 0; }
E
题解
知识点:数论,最小生成树。
根据Kruskal最小生成树,我们贪心选权小的边即可。接下来对每个数 分类讨论:
- 当 时,我们有 权值是最小的。
- 当 时, 不存在 的边,因此我们选 最小的边 。
- 当 时,我们枚举数 ,取 的最小值。注意到, 内素数间距不会很大,因此不用枚举多少个数即可得到 ,此时跳出即可。
时间复杂度
空间复杂度
代码
#include <bits/stdc++.h> using namespace std; using ll = long long; int main() { std::ios::sync_with_stdio(0), cin.tie(0), cout.tie(0); int n, k; cin >> n >> k; ll ans = 0; for (int i = 2;i <= n;i++) { if (1 + k < i) { ans++; continue; } int mi = i; for (int j = i + k + 1;j <= n;j++) { mi = min(mi, gcd(i, j)); if (mi == 1) break; } ans += mi; } cout << ans << '\n'; return 0; }
F
题解
方法一
知识点:枚举,优先队列,离线,贪心。
离线预处理每次操作后的答案,每次对最大的数操作。
当所有数都为 时就不用继续操作了,一个数最多操作 次,因此复杂度是线性的。
时间复杂度
空间复杂度
方法二
知识点:枚举,优先队列,离线,贪心,二分。
注意到每次操作后数字必然 ,我们预处理答案为 所需的操作次数,每次操作都对最大的数取。
对于一个询问 ,只需要二分找到小于等于 的第一个答案即可。
若 小于答案 的操作次数,说明有数没有被操作到,我们对 从大到小排序取第 大的数即答案。
时间复杂度
空间复杂度
代码
方法一
#include <bits/stdc++.h> using namespace std; using ll = long long; int f(int x) { return __builtin_popcount(x); } int ans[200007 * 4]; int main() { std::ios::sync_with_stdio(0), cin.tie(0), cout.tie(0); int n, q; cin >> n >> q; priority_queue<int> pq; for (int i = 1, x;i <= n;i++) { cin >> x; pq.push(x); } int cnt = 0; while (pq.size() && pq.top() > 1) { int mx = f(pq.top()); pq.pop(); pq.push(mx); ans[++cnt] = pq.top(); } while (q--) { int k; cin >> k; cout << ans[min(cnt, k)] << '\n'; } return 0; }
方法二
#include <bits/stdc++.h> using namespace std; using ll = long long; int f(int x) { return __builtin_popcount(x); } int a[200007]; int ans[40]; int main() { std::ios::sync_with_stdio(0), cin.tie(0), cout.tie(0); int n, q; cin >> n >> q; priority_queue<int> pq; for (int i = 1, x;i <= n;i++) { cin >> a[i]; pq.push(a[i]); } int cnt = 0; for (int i = 31;i >= 1;i--) { while (pq.size() && pq.top() > i) { int mx = f(pq.top()); pq.pop(); pq.push(mx); cnt++; } ans[i] = cnt; } sort(a + 1, a + n + 1, greater<int>()); while (q--) { int k; cin >> k; if (k < ans[31]) cout << a[k + 1] << '\n'; else cout << lower_bound(ans + 1, ans + 31 + 1, k, greater<int>()) - ans << '\n'; } return 0; }
G
题解
知识点:贪心,双指针。
显然每次取最小的两个负数,或者取最大的两个正数乘在一起,这样最大。
考虑从小到大排序,用双指针指向最小的两个数和最大的两个数,每次取这两组较大值加入答案。
时间复杂度
空间复杂度
代码
#include <bits/stdc++.h> using namespace std; using ll = long long; ll a[200007]; int main() { std::ios::sync_with_stdio(0), cin.tie(0), cout.tie(0); int n, k; cin >> n >> k; for (int i = 1;i <= n;i++) cin >> a[i]; sort(a + 1, a + n + 1); int l = 1, r = n; ll ans = 0; while (k && l < r) { ll x = a[l] * a[l + 1]; ll y = a[r - 1] * a[r]; if (x >= y) ans += x, l += 2; else ans += y, r -= 2; k--; } cout << ans << '\n'; return 0; }
H
题解
知识点:数学。
取 与 的交叉部分算概率即可。
时间复杂度
空间复杂度
代码
#include <bits/stdc++.h> using namespace std; using ll = long long; int main() { std::ios::sync_with_stdio(0), cin.tie(0), cout.tie(0); int x, l, r; cin >> x >> l >> r; double ans = (min(r, max(l - 1, x - 1)) - l + 1.0) / (r - l + 1); cout << fixed << setprecision(10) << ans << '\n'; return 0; }
I
题解
知识点:BFS,贪心。
一个结论就是,你走过的路可以立刻销毁,并让下一条你走的路变 。因此,对于一条路径,只需要考虑起点出发的第一条路径能否变成 即可,后续的路都可以通过销毁前面的路变 。
我们先处理出起点到终点需要走多少条路,因此bfs时把边权都当 遍历一遍即可。如果到终点的需要走的路数量小于总边数,那么第一条路可以销毁其他路变 ,否则第一条路不能变。
时间复杂度
空间复杂度
代码
#include <bits/stdc++.h> using namespace std; using ll = long long; vector<pair<int, int>> g[200007]; bool vis[200007]; int dis[200007]; queue<int> q; void bfs(int s) { q.push(s); vis[s] = 1; while (q.size()) { int u = q.front(); q.pop(); for (auto [v, w] : g[u]) { if (vis[v]) continue; vis[v] = 1; dis[v] = dis[u] + 1; q.push(v); } } } int main() { std::ios::sync_with_stdio(0), cin.tie(0), cout.tie(0); int n, m; cin >> n >> m; for (int i = 1;i <= m;i++) { int u, v, w; cin >> u >> v >> w; g[u].push_back({ v,w }); g[v].push_back({ u,w }); } bfs(1); if (m > dis[n]) cout << dis[n] << '\n'; else cout << dis[n] - 1 + g[1].front().second << '\n'; return 0; }
J
题解
知识点:模拟,贪心,离线。
显然我们需要离线存储所有事件,事件分为三类:
- 刷题,pro中某人加 。
- 更新,rank更新为当前pro。
- 查询,查询rank中的某个人。
更新事件分为两类:间隔为 的全部更新,查询后的单人更新。注意到,全部更新并不需要更新所有人的rank,我们只需要在 时刻,更新 这段时间刷过题,即pro改变的人,的rank即可,如此全部更新事件就变成单人更新了。
时间复杂度
空间复杂度
代码
#include <bits/stdc++.h> using namespace std; using ll = long long; struct node { int when, what, name, w; friend bool operator<(const node &a, const node &b) { return a.when == b.when ? a.what < b.what : a.when < b.when; } }; int main() { std::ios::sync_with_stdio(0), cin.tie(0), cout.tie(0); int n, T, R; cin >> n >> T >> R; int idx = 0; map<string, int> mp; vector<node> v; for (int i = 1;i <= n;i++) { int op, t; string name; cin >> op >> t >> name; if (!mp.count(name)) mp[name] = ++idx; if (op == 1) { v.push_back({ t,3,mp[name],i }); v.push_back({ t + R,2,mp[name],0 }); } else { int w; cin >> w; v.push_back({ t,1,mp[name],w }); v.push_back({ (t + T - 1) / T * T,2,mp[name],0 }); } } sort(v.begin(), v.end()); vector<ll> pro(idx + 1), rank(idx + 1); vector<pair<int, ll>> ans; for (auto val : v) { if (val.what == 1) pro[val.name] += val.w; else if (val.what == 2) rank[val.name] = pro[val.name]; else ans.push_back({ val.w,rank[val.name] }); } sort(ans.begin(), ans.end()); for (auto val : ans) cout << val.second << '\n'; return 0; }
K
题解
知识点:博弈论,dfs。
考虑dfs搜索操作,边界条件为:
- 若超过了全局最大值 ,则一定平局。
- 若上一轮的人走到了 或之前走到过的点,那么这一轮看作胜。
转移方式为:
- 若能让下一轮必败,那这一轮必胜。
- 否则,若能让下一轮平局,则这一轮平局。
- 否则,这一轮必败。
先搜索除以 的分支,因为收敛更快,需要剪枝不然会超时。
用记忆化搜索也可以写,状态为 表示 已经访问过且大于 第一个被访问过的点为 。但实际上没必要,因为可以推断同一个状态最多被访问两次,不记忆还好写点。
状态空间为 ,但实际上根本跑不满。
时间复杂度
空间复杂度
代码
#include <bits/stdc++.h> using namespace std; using ll = long long; int mx; bool vis[1007]; int dfs(int x) { if (x >= mx) return 0; if (x == 0 || vis[x]) return 1; vis[x] = 1; int l = dfs(x / 2); if (l == -1) return vis[x] = 0, 1; int r = dfs(x + 1); if (r == -1) return vis[x] = 0, 1; vis[x] = 0; return r ? -1 : 0; } int main() { std::ios::sync_with_stdio(0), cin.tie(0), cout.tie(0); int x; cin >> x; mx = x * 2; int f = dfs(x); if (f == 1) cout << "ning" << '\n'; else if (f == -1) cout << "red" << '\n'; else cout << "draw" << '\n'; return 0; }
L
题解
方法一
知识点:容斥原理,排列组合。
容斥原理是最直接的思路。
先有两个结论:
- 到 ,即 的路径总数是 。
- 到斜边各点,即 的路径总数是 ,可以通过二项式系数证明。
我们需要 且不经过禁用点的路径总数,因此我们可以考虑 路径总数减去经过禁用点的路径总数。
路径总数很好求 。
对于只经过一个禁用点 的路径数,我们可以通过 路径数乘以 的路径数,利用结论1和2即可求得,结果是 。
但是,禁用点可能会有路径包含关系。若 和 满足 且 ,则称 包含 。对于 包含 的情况, 的路径可能也通过了 ,因此计算 的路径总数时,需要先求出 和 的路径数,再减去 的路径数。
以此类推,我们枚举所有 个选禁用点通过的合法方案的路径数,根据容斥原理,奇数个点的路径加,偶数个点的路径减,最后就可以得到经过禁用点的路径总数。其中合法方案指,需要存在路径能通过所有选中的禁用点,不能出现一条路径没经过某个点的情况,这要保证任意两点都有包含关系。例如对于 ,若 包含 但 和 没用包含关系,那么我们无法找到一条能同时通过 的路径,所以这是不合法的。
为了方便,我们对点按照 为第一关键字, 为第二关键字从小到大排序。
时间复杂度
空间复杂度
方法二
知识点:拓扑序dp,排列组合。
我们不难发现,我们容斥中 次的加加减减,最后得到的实际上是到达每个禁用点实际路径数,再乘以各自到达斜边各点的路径数,最后求和便是路径总数。
同时,我们还能发现,实际上 次方案有很多重复计算。例如我们要求出到达 的实际路径数,我们只需要知道所有 的前驱 的实际路径数,然后用 到 的全部路径数减去 通过 到 的路径数和,即 ,可以得到 的实际路径数。
注意到,这是一个可以递推的过程,免去了容斥枚举的复杂度。因此,我们可以 建一个DAG,设 为到 的实际路径数,利用拓扑序dp,计算方法同上即可。最后,对于 ,那么 即经过 的实际路径数。
时间复杂度
空间复杂度
方法三
知识点:线性dp,排列组合。
更进一步,我们发现建图的过程中其实可以直接dp了,为了方便我们排好序直接线性dp即可。
时间复杂度
空间复杂度
代码
方法一
#include <bits/stdc++.h> using namespace std; using ll = long long; const int P = 1e9 + 7; const int N = 2e5 + 7; namespace CNM { int qpow(int a, ll k) { int ans = 1; while (k) { if (k & 1) ans = 1LL * ans * a % P; k >>= 1; a = 1LL * a * a % P; } return ans; } int fact[N], invfact[N]; void init(int n) { fact[0] = 1; for (int i = 1;i <= n;i++) fact[i] = 1LL * i * fact[i - 1] % P; invfact[n] = qpow(fact[n], P - 2); for (int i = n;i >= 1;i--) invfact[i - 1] = 1LL * invfact[i] * i % P; } int C(int n, int m) { if (n == m && m == -1) return 1; //* 隔板法特判 if (n < m || m < 0) return 0; return 1LL * fact[n] * invfact[n - m] % P * invfact[m] % P; } } using namespace CNM; using pii = pair<int, int>; int AtoB(pii A, pii B) { auto [x1, y1] = A; auto [x2, y2] = B; return C(x2 + y2 - x1 - y1, x2 - x1); } int n, m; pii pos[20]; int f[20]; int main() { std::ios::sync_with_stdio(0), cin.tie(0), cout.tie(0); cin >> n >> m; for (int i = 1;i <= m;i++) cin >> pos[i].first >> pos[i].second; sort(pos + 1, pos + m + 1); init(n); int ans = 0; for (int i = 0;i < (1 << m);i++) { pii pre = { 1,1 }; auto &[px, py] = pre; bool ok = 1, flag = 0; int mul = 1; for (int j = 0;j < m;j++) { if (!(i & (1 << j))) continue; auto [x, y] = pos[j + 1]; if (px > x || py > y) { ok = 0; break; } mul = 1LL * mul * AtoB(pre, pos[j + 1]) % P; pre = pos[j + 1]; flag ^= 1; } if (!ok) continue; if (flag) (ans -= 1LL * mul * qpow(2, n - px + 1 - py) % P - P) %= P; else (ans += 1LL * mul * qpow(2, n - px + 1 - py) % P) %= P; } cout << ans << '\n'; return 0; }
方法二
#include <bits/stdc++.h> using namespace std; using ll = long long; const int P = 1e9 + 7; const int N = 2e5 + 7; namespace CNM { int qpow(int a, ll k) { int ans = 1; while (k) { if (k & 1) ans = 1LL * ans * a % P; k >>= 1; a = 1LL * a * a % P; } return ans; } int fact[N], invfact[N]; void init(int n) { fact[0] = 1; for (int i = 1;i <= n;i++) fact[i] = 1LL * i * fact[i - 1] % P; invfact[n] = qpow(fact[n], P - 2); for (int i = n;i >= 1;i--) invfact[i - 1] = 1LL * invfact[i] * i % P; } int C(int n, int m) { if (n == m && m == -1) return 1; //* 隔板法特判 if (n < m || m < 0) return 0; return 1LL * fact[n] * invfact[n - m] % P * invfact[m] % P; } } using namespace CNM; using pii = pair<int, int>; int n, m; pii pos[20]; int AtoB(pii A, pii B) { auto [x1, y1] = A; auto [x2, y2] = B; return C(x2 + y2 - x1 - y1, x2 - x1); } vector<int> g[20]; int f[20]; int deg[20]; queue<int> q; void toposort() { for (int i = 1;i <= m;i++) if (!deg[i]) q.push(i); while (!q.empty()) { int u = q.front(); q.pop(); (f[u] += AtoB({ 1,1 }, pos[u])) %= P; for (auto v : g[u]) { (f[v] -= 1LL * f[u] * AtoB(pos[u], pos[v]) % P - P) %= P; deg[v]--; if (!deg[v]) q.push(v); } } } int main() { std::ios::sync_with_stdio(0), cin.tie(0), cout.tie(0); cin >> n >> m; for (int i = 1;i <= m;i++) cin >> pos[i].first >> pos[i].second; for (int i = 1;i <= m;i++) { auto [x1, y1] = pos[i]; for (int j = 1;j <= m;j++) { if (i == j) continue; auto [x2, y2] = pos[j]; if (x2 <= x1 && y2 <= y1) { g[j].push_back(i); deg[i]++; } } } init(n); toposort(); ll ans = qpow(2, n - 1); for (int i = 1;i <= m;i++) { auto [x, y] = pos[i]; (ans -= 1LL * f[i] * qpow(2, n - x + 1 - y) % P - P) %= P; } cout << ans << '\n'; return 0; }
方法三
#include <bits/stdc++.h> using namespace std; using ll = long long; const int P = 1e9 + 7; const int N = 2e5 + 7; namespace CNM { int qpow(int a, ll k) { int ans = 1; while (k) { if (k & 1) ans = 1LL * ans * a % P; k >>= 1; a = 1LL * a * a % P; } return ans; } int fact[N], invfact[N]; void init(int n) { fact[0] = 1; for (int i = 1;i <= n;i++) fact[i] = 1LL * i * fact[i - 1] % P; invfact[n] = qpow(fact[n], P - 2); for (int i = n;i >= 1;i--) invfact[i - 1] = 1LL * invfact[i] * i % P; } int C(int n, int m) { if (n == m && m == -1) return 1; //* 隔板法特判 if (n < m || m < 0) return 0; return 1LL * fact[n] * invfact[n - m] % P * invfact[m] % P; } } using namespace CNM; using pii = pair<int, int>; int AtoB(pii A, pii B) { auto [x1, y1] = A; auto [x2, y2] = B; return C(x2 + y2 - x1 - y1, x2 - x1); } int n, m; pii pos[20]; int f[20]; int main() { std::ios::sync_with_stdio(0), cin.tie(0), cout.tie(0); cin >> n >> m; for (int i = 1;i <= m;i++) cin >> pos[i].first >> pos[i].second; sort(pos + 1, pos + m + 1); init(n); int ans = qpow(2, n - 1); for (int i = 1;i <= m;i++) { auto [x1, y1] = pos[i]; f[i] = AtoB({ 1,1 }, pos[i]); for (int j = 1;j < i;j++) { auto [x2, y2] = pos[j]; if (x2 <= x1 && y2 <= y1) (f[i] -= 1LL * f[j] * AtoB(pos[j], pos[i]) % P - P) %= P; } (ans -= 1LL * f[i] * qpow(2, n - x1 + 1 - y1) % P - P) %= P; } cout << ans << '\n'; return 0; }
本文来自博客园,作者:空白菌,转载请注明原文链接:https://www.cnblogs.com/BlankYang/p/17106882.html
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 一个费力不讨好的项目,让我损失了近一半的绩效!
· 实操Deepseek接入个人知识库
· CSnakes vs Python.NET:高效嵌入与灵活互通的跨语言方案对比
· Plotly.NET 一个为 .NET 打造的强大开源交互式图表库
· 【.NET】调用本地 Deepseek 模型