2025牛客寒假算法基础集训营3补题笔记
题目难度顺序大致为:\(A、M、F、L、C、\)
\(easy\):\(A、M、F、L、C\)
太难了这场。。。E题卡了3个多小时。。。
A.智乃的博弈游戏
题意
有\(n\) 个石头,两人轮流取石头。每次能取小于石头个数且与石头个数互质的数量,当某人取时只有一颗石头则获胜。问先手是否可以必胜。
思路
博弈论,思维
首先,相邻的奇数是互质的,即当 \(n\) 是奇数时,\(n - 2\) 一定和其互质。所以,奇数的局面一定可以留下2个石头给下一个人,而2个石头是必输的局面。
接着考虑当 \(n\) 是偶数时,偶数只能和奇数互质,而偶数-奇数=奇数,留奇数个石头给下一个人,那么下一个人是必胜的,理由如上。
综上所述,先手是奇数个石头必胜,偶数个石头必败。
注意开long long!
时间复杂度: \(O(1)\)
代码
点击查看代码
#include <iostream> #define ll long long using namespace std; ll x; void solve() { cin >> x; if (x % 2) cout << "Yes"; else cout << "No"; } int main() { int t = 1; while (t --) solve(); return 0; }
M.智乃的牛题
题意
给一个字符串,询问该字符串的字母能否组成单词 \(nowcoder\)。
思路
模拟
记录每个字母次数,然后查看是否可以拼成 \(nowcoder\) 即可。
时间复杂度:\(O(n)\)
代码
点击查看代码
#include <iostream> #include <map> using namespace std; string s; map<char, int> mp; string tmp = "nowcoder"; void solve() { cin >> s; for (auto i : s) mp[i] ++; for (auto i : tmp) if (i == 'o' && mp[i] == 2) continue; else if (mp[i] == 1) continue; else return void(cout << "I AK IOI"); cout << "happy new year"; } int main() { int t = 1; while (t --) solve(); return 0; }
F.智乃的捉迷藏
题意
三个人分别可以看见躲藏在面前三个位置上的人,一共有n个人躲藏,询问三人是否可以分别看见\(a、b、c\)个人。
即:\(a = s_1 + s_2 + s_3, b = s_3 + s_4 + s_5, c = s_5 + s_6 + s_1\)
思路
数学
解上面的等式:
时间复杂度:\(O(1)\)
代码
点击查看代码
#include <iostream> using namespace std; int n; int a, b, c; void solve() { cin >> n >> a >> b >> c; if (a + b + c <= 2 * n && a + b + c >= n) cout << "Yes" << '\n'; else cout << "No" << '\n'; } int main() { ios::sync_with_stdio(false); cin.tie(0), cout.tie(0); int t = 1; cin >> t; while (t --) solve(); return 0; }
L.智乃的三角遍历
题意
给一个 \(n\) 层的三角形,询问是否可以一笔不重不漏的经过三角形上的所有边,如果可以就给出一组序列表示经过的顶点顺序。
思路
模拟
顺序不止一种,自己纸上找到一种规律然后想办法模拟下来就好了。
我的顺序是每层画完后接着画下一层
时间复杂度:\(O(n^2)\)
代码
点击查看代码
#include <iostream> #include <vector> using namespace std; const int M = 1000 + 10; int n, m; vector<int> g[M]; vector<int> ans; void solve() { cin >> n; int num = 1; for (int i = 0; i <= n; i ++) { int j = i + 1; while (j --) g[i].emplace_back(num ++); } cout << "Yes" << '\n'; for (int i = 0; i <= n; i ++) { for (int j = 0; j < g[i].size(); j ++) cout << g[i][j] << ' '; if (i + 1 > n) continue; for (int j = g[i + 1].size() - 2; j >= 1; j --) cout << g[i + 1][j] << ' ' << g[i][j - 1] << ' '; } for (int i = n - 1; i >= 0; i --) cout << g[i].back() << ' '; } int main() { ios::sync_with_stdio(false); cin.tie(0), cout.tie(0); int t = 1; while (t --) solve(); return 0; }
C.智乃的Notepad(Easy version)
题意
给 \(n\) 个单词,询问最少敲击键盘次数,在使用键盘输入过程中出现所有单词,删除键敲击也算一次。
思路
思维、贪心、字典树、\(dfs\)
存储多个单词,可以想到使用字典树来将所有单词存下。
那么问题就会转换为,遍历这颗字典树上所有节点的最小步数。
可以想到,如果是从根节点出发后再回到根节点,那么所有节点都会遍历两次。而我们并不需要回到根节点,要么从贪心角度出发,我们只需要停留在最深那个节点上即可,这样我们就不要将这最深的路径上的节点又遍历一次,答案为:所有节点个数乘二减去最深的层数。
时间复杂度:\(O(\sum|s_i| \times 26)\)
代码
点击查看代码
#include <iostream> #define ll long long #define si(x) int(x.size()) using namespace std; const int N = 1e6 + 10; const int M = 30; int n, m; int son[N][M], idx; ll maxv; void insert(string s) { int p = 0; for (int i = 0; i < si(s); i ++) { int u = s[i] - 'a'; if (!son[p][u]) son[p][u] = ++ idx; p = son[p][u]; } } ll dfs(int u, ll stp) { ll res = 1; maxv = max(maxv, stp); if (u == 0) res = 0; for (char i = 'a'; i <= 'z'; i ++) { int v = i - 'a'; if (!son[u][v]) continue; ll sum = dfs(son[u][v], stp + 1); res += sum; } return res; } void solve() { cin >> n >> m; for (int i = 0; i < n; i ++) { string s; cin >> s; insert(s); } int l, r; cin >> l >> r; int ans = dfs(0, 0); cout << ans * 2ll - maxv; } int main() { ios::sync_with_stdio(false); cin.tie(0), cout.tie(0); int t = 1; while (t --) solve(); return 0; }
E.智乃的小球
题意
给 \(n\) 个小球,以及每个小球的位置 \(p_i\) 和方向 \(v_i\) ,求第 \(k\) 对完全弹性碰撞在什么时刻发生。
思路
二分、双指针
首先,假设在时刻 \(x\) 时发生了 \(k\) 对完全弹性碰撞,那么在大于 \(x\) 的时刻肯定发生了不小于 \(k\) 对的完全弹性碰撞,在小于 \(x\) 的时刻肯定没有达到 \(k\) 对完全弹性碰撞,所以时刻是具备二分性的,考虑用二分来解决。
接着,就是判断二分的时刻下发生了多少对完全弹性碰撞。
在此之前,还要知道一个点,就是两个小球完全弹性碰撞,可以看成两个小球交错而过。
假设现在向右的小球的位置为:2 4 6 8,向左的小球的位置为:3 5 7,模拟小球运动:当 2 与 3 5两个小球交错而过(即发生了两次完全弹性碰撞,后面的也如此)那么肯定的,4 在此之前一定已经和 5 相遇了一次,现在我们二分了一个时刻,可以预知向右的小球在 \(x\) 时刻后会在 \(p_i + x\) 的位置上,那么在 \([p_i, p_i + x]\) 区间内的向左的小球都会相遇一次,由此我们可以统计出所有向右的小球的碰撞总次数,再和 \(k\) 进行比较。
再计算 \([p_i, p_i + x]\) 区间内的相遇次数时,不能暴力枚举所有向右的小球,可以使用双指针去检测区间内的向左的小球的左右下标。
代码
点击查看代码
#include <iostream> #include <algorithm> #include <vector> #include <iomanip> using namespace std; typedef long long ll; int n, k; vector<int> u, v; bool check(ll x) { ll res = 0; int l = 0, r = 0; for (auto i : u) { while (l < v.size() && v[l] < i) l ++; while (r < v.size() && v[r] <= i + x) r ++; res += r - l; } return res >= k; } int main() { cin >> n >> k; for (int i = 0; i < n; i ++) { int p, y; cin >> p >> y; if (y == 1) u.emplace_back(p); else v.emplace_back(p); } sort(u.begin(), u.end()); sort(v.begin(), v.end()); ll l = 1, r = 1e9; while (l < r) { ll mid = (l + r) >> 1; if (check(mid)) r = mid; else l = mid + 1; } if (!check(l)) cout << "No"; else cout << "Yes" << '\n' << fixed << setprecision(6) << 1.0 * l / 2; return 0; }
J.智乃画二叉树
题意
根据题目数据画二叉树。
思路
超级大模拟,不会。。。
代码
略
K.智乃的逆序数
题意
构造一个序列包含若干个值域互不相交子序列,且逆序对恰好为 \(K\) 对。
思路
求逆序数的方法有:冒泡排序、归并排序、树状数组、线段树等。
先简单化考虑,因为给出的是几段大小连续且重排的序列,那么先得出逆序数最少和逆序数最多的情况。
逆序数最少的情况就是每段从小到大进行段间排序,逆序数为每段内的逆序数之和。
最大的情况就是每段从大到小进行段间排序,逆序数要根据冒泡排序算出。
我们可以从最少的逆序数的情况开始构造,如果逆序数量不够,就在段间把各自两个元素进行位置交换,知道逆序数达到 \(k\) 对。
代码
点击查看代码
#include <iostream> #include <algorithm> #include <vector> using namespace std; typedef pair<int, int> PII; int n, k; vector<PII> a; int main() { cin >> n >> k; vector<vector<int>> b(n); for (int i = 0; i < n; i ++) { int l; cin >> l; for (int j = 0; j < l; j ++) { int x; cin >> x; b[i].emplace_back(x); } } sort(b.begin(), b.end(), [](vector<int> a, vector<int> b){ return a[0] < b[0]; }); for (int i = 0; i < n; i ++) for (int j : b[i]) a.emplace_back(i, j); int sum = 0; for (int i = 0; i < a.size(); i ++) for (int j = i + 1; j < a.size(); j ++) if (a[i] > a[j]) sum ++; if (k < sum) return cout << "No", 0; k -= sum; auto print = [&]() -> void { cout << "Yes" << '\n'; for (auto it : a) cout << it.second << ' '; }; if (k == 0) print(); else { for (int i = 0; i < a.size() - 1; i ++) for (int j = 0; j < a.size() - 1 - i; j ++) if (a[j].first == a[j + 1].first) continue; else if (k && a[j].second < a[j + 1].second) { swap(a[j], a[j + 1]); k --; } if (k) cout << "No"; else print(); } return 0; }
本文作者:Natural_TLP
本文链接:https://www.cnblogs.com/Natural-TLP/p/18721657
版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步