蔚来杯2022牛客暑期多校训练营5 ABCDFGHK
A
题解
知识点:图论,dp。
暴力建图,连接所有点的双向通路,除了原点是单向的,并且把路径长度作为权值。
随后,从原点出发(,其他点负无穷,保证从原点出发),按照权值从大到小(大的先走小的后走,相等属于同一阶段,满足拓扑序)进行dp。
对于路径 的状态转移方程是 ,其中 分别存储上一次结果和这一次结果,做到滚动数组节省空间,随后把 还给 ,下次dp前把 初始化负无穷。
最后,找到所有点的最大值。
时间复杂度
空间复杂度
代码
#include <bits/stdc++.h> using namespace std; struct node { int u, v, w; }; int main() { std::ios::sync_with_stdio(0), cin.tie(0), cout.tie(0); int n; cin >> n; pair<int, int> p[2007]; for (int i = 1;i <= n;i++) { int u, v; cin >> u >> v; p[i] = { u,v }; } vector<node> e; for (int i = 0;i <= n;i++)///暴力连边,只从原点出发 for (int j = 1;j <= n;j++) if (i != j)e.push_back({ i,j,(p[i].first - p[j].first) * (p[i].first - p[j].first) + (p[i].second - p[j].second) * (p[i].second - p[j].second) }); sort(e.begin(), e.end(), [&](node a, node b) { return a.w > b.w; });///从大到小距离走 vector<int> f(n + 1, -2e9), g(n + 1, -2e9); int i = 0, j = 0; f[0] = 0;///解锁原点 while (i < e.size()) { vector<node> t; while (j < e.size() && e[i].w == e[j].w) t.push_back(e[j++]);///把下一个大小的边都加入 for (auto ed : t) g[ed.v] = -2e9;///初始化 for (auto ed : t) g[ed.v] = max(g[ed.v], f[ed.u] + 1);///更新到下一个点吃到最多的数量 for (auto ed : t) f[ed.v] = max(g[ed.v], f[ed.v]);///滚动 i = j; } cout << *max_element(f.begin(), f.end()) << '\n'; return 0; }
B
题解
知识点:二分,贪心,排序。
答案单调是可以二分的,所以优先考虑二分。发现对于一个答案要选择最便宜的去验证答案,因此每次需要对序列排序(因为排序规则和答案有关),满足关系式:
取 个如果价值总和小于等于钱数,证明答案可行。
时间复杂度
空间复杂度
代码
#include <bits/stdc++.h> #define ll long long using namespace std; int n, m; struct node { ll val; ll id; }a[100007]; bool check(int mid) { sort(a + 1, a + n + 1, [&](node a, node b) { return a.val + mid * a.id < b.val + mid * b.id; }); ll sum = 0; for (int i = 1;i <= mid;i++) sum += a[i].val + mid * a[i].id; return sum <= m; } int main() { std::ios::sync_with_stdio(0), cin.tie(0), cout.tie(0); cin >> n >> m; for (int i = 1;i <= n;i++) cin >> a[i].val, a[i].id = i; int l = 0, r = n; while (l <= r) { int mid = l + r >> 1; if (check(mid)) l = mid + 1; else r = mid - 1; } cout << r << '\n'; return 0; }
C
题解
知识点:思维。
对某位Y/N数进行分类讨论,如下列表格:
YES数/NO数 | 0 | 1 | >1 |
---|---|---|---|
0 | -1 | -1/0 | 0 |
1 | -1/1 | -1 | 0 |
>1 | 1 | 1 | -1 |
存在四种情况能确定,两种可能确定,剩下三种不确定直接 。
确定的四种:
- Y > 1且N = 0,不可能是错误询问,直接确定为 。
- N > 1且Y = 0,同上确定为 。
- Y > 1且N = 1,确定唯一错误询问,并确定为 。
- N > 1且Y = 1,同上确定为 。
可能确定的两种:
- Y = 1且N = 0,最终错误询问被发现时可确定为 ,否则不确定,因此将此类计数,最终判断这种情况。
- N = 1且Y = 0,同上可能确定为 。
不确定的三种:
- Y = 0且N = 0,显然不确定。
- Y = 1且N = 1,发现错误询问,但依然不确定。
- Y > 1且N > 1,错误询问超额。
时间复杂度
空间复杂度
代码
#include <bits/stdc++.h> using namespace std; struct bit { int y, n; }a[100007]; bool ans[100007]; int main() { std::ios::sync_with_stdio(0), cin.tie(0), cout.tie(0); int n; cin >> n; int x; string s; for (int i = 1;i <= 3 * n;i++) { cin >> x >> s; if (s == "YES") a[x].y++; else a[x].n++; } bool fault = false; bool ok = true; int cnt = 0; for (int i = 0;i < n;i++) { if (a[i].y > 1 && a[i].n == 0) ans[i] = 1; else if (a[i].y == 0 && a[i].n > 1) ans[i] = 0; else if (!fault && a[i].y > 1 && a[i].n == 1) ans[i] = 1, fault = 1; else if (!fault && a[i].y == 1 && a[i].n > 1) ans[i] = 0, fault = 1; else if (a[i].y == 1 && a[i].n == 0) ans[i] = 1, cnt++; else if (a[i].y == 0 && a[i].n == 1) ans[i] = 0, cnt++; else ok = false; } if (!ok || !fault && cnt) cout << -1; else for (int i = 0;i < n;i++) cout << ans[i]; cout << '\n'; return 0; }
D
题解
知识点:树形dp。
题目要求,树中满足度为 的节点的权值都相同(0/1)的连通子图个数。
因为全为 和全为 两种情况其实解决方法一样,所以可以同一个方法跑两次即可。
考虑度为 的节点颜色为 。设 表示为以编号 的节点为根的子树的方案数。对于根 及其子节点 ,每个子节点 都有 种选择方案使得以 为根的子树符合条件,加上不选 分支的一种,共有 种方案。对所有子节点 ,都可以独立的选和不选,所以进行乘法原理的运算,得到选择子节点 的所有方案数为 。
但有一种情况是所有子节点都不选,只留个根。这种方案会使这个节点成为一个度为 的点(因为是子树),颜色必须要符合 才可以作为一种方案。因此这种情况要特判,故 。
接下来是记录答案,如果 ,则作为一个独立的以 为根的树(不是别的节点的子树)的所有方案和是子树的所有方案 是等价的,因为 无论如何都符合条件;如果 ,则作为一个独立树,当只选了一个子节点的情况是不可行的,此时根节点 是度为 的,与 的情况不同(因为是子树, 只有在什么都不选的情况才会度为 ),因此要从 中减去 。
值得注意的是,无论 是否同色,当其本身成为一个只有一个节点的图时,都是满足条件的。这种情况其实已经算在了 中 的一种里,不需要额外计算,否则会重复。
无根树可以以任何节点为根,因此从 开始没什么问题。
时间复杂度
空间复杂度
代码
#include <bits/stdc++.h> using namespace std; const int mod = 1e9 + 7; bool a[300007]; vector<int> g[300007]; bool flag; int dp[300007]; int ans; void dfs(int u, int fa) { int sum = 0, mul = 1; for (int v : g[u]) { if (v == fa) continue; dfs(v, u); mul = 1LL * mul * (dp[v] + 1) % mod;///(子节点方案+不选子节点的一个方案)进行乘法原理 sum = (0LL + sum + dp[v]) % mod;///只选了一个子节点的方案数 } dp[u] = (mul - 1 + (a[u] == flag)) % mod;///mul减去一个子节点都不选的可能加上自己可能符合要求的可能 if (a[u] == flag) ans = (0LL + ans + dp[u]) % mod;///当前节点符合要求,答案加上dp[u]即可 else ans = (0LL + ans + dp[u] - sum + mod) % mod;///当前节点不符合要求,则不能成为度为1的节点,减去选一个子节点的所有方案 } 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++) { char ch; cin >> ch; a[i] = ch - '0'; } for (int i = 1;i <= n - 1;i++) { int u, v; cin >> u >> v; g[u].push_back(v); g[v].push_back(u); } flag = 1; dfs(1, 0); flag = 0; dfs(1, 0); cout << ans << '\n'; return 0; }
F
题解
知识点:计算几何。
纯纯的计算几何(没学过,函数都是现场造的,好难qwq。
注意到是从上往下看,因此考虑从下往上遍历,考虑每一个被上面盖住的弧长即可。
实现过程上,先考虑两个圆 被 覆盖,且是相交的情况。发现只需要用一个余弦定理计算以 半径为对边, 与 为邻边的三角形的夹角弧度 ,然后乘以二就是被覆盖的弧长弧度;如果两个圆相离或者后者包含于前者则弧度为 ;如果前者包含于后者,是全覆盖,弧度为 。
但事实上不止一个圆覆盖,因此要考虑重复覆盖的问题,这涉及到了区间合并,因此考虑以 轴正半轴为基准,逆时针为正方向,形成区间 ,同时刚好满足 atan2
函数的特性。
先计算 的区间弧度 ,然后加减 ,得到始边弧度和终边弧度。同时要考虑始边弧度 和终边弧度 的特判,要把一个角拆成两个角,比如 拆成 与 。
随后按起边从小到大排序合并区间,要注意有可能该圆不被任何圆覆盖, 为 ,因此不能以第一个弧段为初始条件,应该以 类似的为初始条件,或者以 额外加入数组作为终止条件,顺便还可以把最后一段有效区间加上,我个人喜欢后面这种。
时间复杂度
空间复杂度
代码
#include <bits/stdc++.h> using namespace std; const double pi = acos(-1.0); struct Point { double x; double y; }; struct Circle { Point A; double r; }a[1007]; double sqr(double x) { return x * x; } double dist2(Point A, Point B) { return sqr(A.x - B.x) + sqr(A.y - B.y); } double rad(double a, double b, double c) {///余弦定理,a对边,b,c邻边 return acos((b * b + c * c - a * a) / (2 * b * c)); } double xrad(Point A, Point B) {///与x轴正方向夹角[-pi,pi] return atan2(A.y - B.y, A.x - B.x);///斜率y/x,且由象限决定 } struct node { double x; double y; }xy[1007 << 1]; 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].A.x >> a[i].A.y >> a[i].r; double ans = 0; for (int i = 1;i <= n;i++) { int cnt = 0; for (int j = i + 1;j <= n;j++) { ++cnt; double len2 = dist2(a[i].A, a[j].A); if (len2 >= sqr(a[i].r + a[j].r) || sqrt(len2) <= a[i].r - a[j].r) {///相离 或 j内含于i xy[cnt] = { 0,0 }; continue; } else if (sqrt(len2) <= a[j].r - a[i].r) {///i内含于j,全盖住了 xy[cnt] = { -pi,pi }; break; } double xita = rad(a[j].r, a[i].r, sqrt(len2)); double base = xrad(a[j].A, a[i].A); xy[cnt].x = base - xita; xy[cnt].y = base + xita; if (xy[cnt].x < -pi) {///起边小于-pi那就分成[起边+2pi,pi],[-pi,终边] cnt++; xy[cnt] = { xy[cnt - 1].x + 2 * pi, pi }; xy[cnt - 1].x = -pi; } else if (xy[cnt].y > pi) {///同上 cnt++; xy[cnt] = { -pi, xy[cnt - 1].y - 2 * pi }; xy[cnt - 1].y = pi; } } sort(xy + 1, xy + cnt + 1, [&](node a, node b) { return a.x < b.x; });///按起边从小到大排序,开始合并区间 xy[++cnt] = { pi + 1,pi + 1 };///终止特殊判断,方便把最后一段有效段加上 ans += 2 * pi * a[i].r;///先加上周长 double lpre = -pi - 1, rmax = -pi - 1;///初始化负无穷,不能初始化第一个,因为有可能一个都没有(如果上面加了终止判断,可以不考虑这个) for (int j = 1;j <= cnt;j++) {///合并区间 if (xy[j].x >= rmax) { ans -= (rmax - lpre) * a[i].r;///减去一个区间的周长 lpre = xy[j].x; } rmax = max(rmax, xy[j].y); } } cout << fixed << setprecision(15) << ans << '\n'; return 0; }
G
题解
方法一
知识点:manachar。
不会qwq。
方法二
知识点:回文自动机。
模板题。不多说(因为也不会说qwq。
时间复杂度
空间复杂度
代码
方法一
无
方法二
#include<bits/stdc++.h> #define ll long long using namespace std; const int N = 5e5 + 10; char s[N]; int n, lst, len[N]; int now, tot = 1, fail[N], cnt[N], t[N][26]; int getfail(int u, int p) { while (p - len[u] - 1 <= 0 || s[p - len[u] - 1] != s[p]) u = fail[u]; return u; } int insert(char c, int id) { int p = getfail(now, id); if (!t[p][c - 'a']) { fail[++tot] = t[getfail(fail[p], id)][c - 'a']; t[p][c - 'a'] = tot; len[tot] = len[p] + 2; cnt[tot] = cnt[fail[tot]] + 1; } return cnt[now = t[p][c - 'a']]; } ll ans[3]; int main() { std::ios::sync_with_stdio(0), cin.tie(0), cout.tie(0); cin >> n; cin >> s + 1; fail[0] = 1, len[1] = -1; for (int i = 1;i <= n;i++) { lst = insert(s[i], i); if (s[i] == 'k') ans[0] += lst; else if (s[i] == 'f') ans[1] += lst; else if (s[i] == 'c') ans[2] += lst; } cout << ans[0] << ' ' << ans[1] << ' ' << ans[2] << '\n'; return 0; }
H
题解
知识点:数学,计算几何。
画图,求并集面积。
显然, 。
(把union area看成交集QAQ,要被英语退役了)
时间复杂度
空间复杂度
代码
#include <bits/stdc++.h> using namespace std; int main() { std::ios::sync_with_stdio(0), cin.tie(0), cout.tie(0); double n; cin >> n; cout << fixed << setprecision(15) << n * n / 2.0 + n * n * acos(-1.0) / 8.0 << '\n';///圆和多边形面积并集 return 0; }
K
题解
知识点:数学。
首先如果 则一定拿不到。
然后根据鸽巢原理。因为一对耳机有两个头,最坏情况是每对都只拿了一个头即 个耳机,还要补上 个耳机凑成 对,因此一共要拿 个耳机。
时间复杂度
空间复杂度
代码
#include <bits/stdc++.h> using namespace std; int main() { std::ios::sync_with_stdio(0), cin.tie(0), cout.tie(0); int n, k; cin >> n >> k; if (n - k <= k) { cout << -1 << '\n'; return 0; } cout << n + 1 << '\n'; return 0; }
本文来自博客园,作者:空白菌,转载请注明原文链接:https://www.cnblogs.com/BlankYang/p/16544097.html
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧