AtCoder Beginner Contest 335
A - 2023 (abc335 A)
题目大意
给定一个字符串,将最后一位改成4
。
解题思路
模拟即可。
神奇的代码
#include <bits/stdc++.h> using namespace std; using LL = long long; int main(void) { ios::sync_with_stdio(false); cin.tie(0); cout.tie(0); string s; cin >> s; s.back() = '4'; cout << s << '\n'; return 0; }
B - Tetrahedral Number (abc335 B)
题目大意
给定,按字典序输出非负整数,使得
解题思路
只有 ,直接 枚举判断即可。
神奇的代码
#include <bits/stdc++.h> using namespace std; using LL = long long; int main(void) { ios::sync_with_stdio(false); cin.tie(0); cout.tie(0); int n; cin >> n; for (int i = 0; i <= n; ++i) for (int j = 0; j <= n; ++j) for (int k = 0; k <= n; ++k) if (i + j + k <= n) cout << i << ' ' << j << ' ' << k << '\n'; return 0; }
C - Loong Tracking (abc335 C)
题目大意
二维网格,贪吃蛇,移动,进行次操作,分两种
- 指定贪吃蛇下一步移动的方向
- 指定,输出贪吃蛇的第 个部位的坐标。
解题思路
每移动一次,只有头部到了新的坐标,其他部分的坐标都变成前一个。
如果我们把每个部分的坐标按顺序放在一个队列里,队尾是头部坐标,队头是尾部坐标,每次移动相当于一次出队和一次入队。
但stl
的队列queue
不支持随机访问,因此用vector
模拟这个队列即可。
出队其实没有必要操作,不断push_back
即可。问第个部位坐标就从队尾倒着数即可。
神奇的代码
#include <bits/stdc++.h> using namespace std; using LL = long long; int main(void) { ios::sync_with_stdio(false); cin.tie(0); cout.tie(0); int n, q; cin >> n >> q; vector<array<int, 2>> pos(n); for (int i = 0; i < n; ++i) { pos[i] = {n - i, 0}; } map<char, array<int, 2>> dir{ {'U', {0, 1}}, {'D', {0, -1}}, {'L', {-1, 0}}, {'R', {1, 0}}}; while (q--) { int op; cin >> op; if (op == 1) { string s; cin >> s; auto [x, y] = pos.back(); auto [dx, dy] = dir[s[0]]; x += dx; y += dy; pos.push_back({x, y}); } else if (op == 2) { int s; cin >> s; auto [x, y] = *(pos.end() - s); cout << x << ' ' << y << '\n'; } } return 0; }
D - Loong and Takahashi (abc335 D)
题目大意
给定一个的二维网格,奇数,给每个格子一个数字,要求每个数字仅使用一次,且相邻数字的格子相邻,且正中间的格子不能是数字,而是T
。
给出一种构造方法。
解题思路
按照样例的构造方式,一个螺旋回字构造即可。
每次一个螺旋回字构造就填充最外围的一圈,起点依次为 ,最后恰好到正中间,因此能恰好填满。
神奇的代码
#include <bits/stdc++.h> using namespace std; using LL = long long; int main(void) { ios::sync_with_stdio(false); cin.tie(0); cout.tie(0); int n; cin >> n; int mid = n / 2; vector<vector<int>> tu(n, vector<int>(n, 0)); int num = 1; array<array<int, 2>, 4> dir{{{0, 1}, {1, 0}, {0, -1}, {-1, 0}}}; int cur = 0; int x = 0, y = 0; while (x != mid || y != mid) { tu[x][y] = num; auto [dx, dy] = dir[cur]; int nx = x + dx, ny = y + dy; while (nx < 0 || nx >= n || ny < 0 || ny >= n || tu[nx][ny] != 0) { cur = (cur + 1) % 4; auto [dx, dy] = dir[cur]; nx = x + dx, ny = y + dy; } ++num; x = nx, y = ny; } for (int i = 0; i < n; ++i) for (int j = 0; j < n; ++j) { if (i == mid && j == mid) cout << 'T' << ' '; else cout << tu[i][j] << " \n"[j == n - 1]; } return 0; }
E - Non-Decreasing Colorful Path (abc335 E)
题目大意
给定一张无向图,点有点权。
找一条从的路径,其点权不递减,且不同的数的个数最多。不存在则输出。
解题思路
注意到这个路径要求点权是一个不严格递增的情况,这明显存在一个决策的方向性,即点权大的一定从点权小的求得答案,当点权小的答案都求完了,那么该点权大的答案也求完了,即固定了,知晓了。
因此就直接设表示到达点 时的最大的答案(不同的数的个数最多),按照点权从小到大转移,若点权相同则按照答案从大到小转移。用优先队列维护转移顺序即可。往后转移。初始条件为
也可以根据点权构造边权,注意到图如果有环,其边权一定都是,缩环后变成一张DAG(有向无环图),直接拓扑 即可,其实跟上面是一样的。
神奇的代码
#include <bits/stdc++.h> using namespace std; using LL = long long; const int inf = 1e9; int main(void) { ios::sync_with_stdio(false); cin.tie(0); cout.tie(0); int n, m; cin >> n >> m; vector<int> a(n); for (auto& i : a) cin >> i; vector<vector<array<int, 2>>> edge(n); auto add = [&](int u, int v) { if (a[u] <= a[v]) edge[u].push_back({v, -(a[u] != a[v])}); }; for (int i = 0; i < m; i++) { int u, v; cin >> u >> v; --u, --v; add(u, v); add(v, u); } vector<int> dis(n, inf); dis[0] = -1; priority_queue<array<int, 3>> q; q.push({-a[0], -dis[0], 0}); while (!q.empty()) { auto [_, d, u] = q.top(); q.pop(); if (dis[u] != -d) continue; for (auto [v, w] : edge[u]) { if (dis[v] > dis[u] + w) { dis[v] = dis[u] + w; q.push({-a[v], -dis[v], v}); } } } debug(dis); cout << max(0, -dis.back()) << '\n'; return 0; }
F - Hop Sugoroku (abc335 F)
题目大意
给定一个个数的数组, 你在处。当你在 处时,你可以移动到 处,为正整数,也可以不移动。
问你可以移动到的位置的集合数量。
解题思路
注意到每次移动一定是往右边移动,具有明显的决策方向性,可以设表示移动到第 个位置时,移动位置的集合数量(即这个集合中的最大值为 的集合数量)。
转移式则为,初始条件为 。
上述的复杂度为 ,考虑如何优化转移。
但思考下会发现上述转移最坏情况就是 ,当 全部为时 ,就要对每个都累加。但因为是对每个 都累加,事实上我们可以预先处理出前缀和,这样就可以 。
考虑这个前缀和的形式是怎样的,我们对的值进行了枚举,假设为 ,那满足转移条件的 则为,我们要把这些 的 累加,即 ,这样。
但因为是 ,和同一个数量级,上述转移 复杂度还是。
但注意到如果比较大时,满足条件的 的数量是很少的,这种情况下我们可以暴力转移,这个转移是往后转移,即当前为,则 。
这期间就有个分界点,我们设分界点为
- 当时,我们暴力更新后面的,这样的的数量(即 的数量)不超过个。更新的复杂度是
- 当时,由于转移的数量 很多,我们就先不转移,先保存在 数组里(可以看成是懒标记),即 。更新的复杂度是
这样,对于后续的一个的 的值,注意到原始转移式是,我们已经把情况一(即)的累加进 里了,还剩下的没累加进来,因此此时再 即可。更新的复杂度是
最终的时间复杂度是。
注意到上述转移里,我们把和 是割裂开了,考虑 的所有取值情况,然后分成两类分别处理。
这其实也是一类经典的优化方法,即根号分治,即存在两种形式的转移,且在不同数量级下时间复杂度各有优势,然后结合起来, 是一个经典的分界点
神奇的代码
#include <bits/stdc++.h> using namespace std; using LL = long long; const int mo = 998244353; int main(void) { ios::sync_with_stdio(false); cin.tie(0); cout.tie(0); int n; cin >> n; int half = sqrt(n); vector<vector<int>> sum(half, vector<int>(half, 0)); vector<int> a(n); for (auto& i : a) cin >> i; vector<int> dp(n); dp[0] = 1; for (int i = 0; i < n; ++i) { for (int j = 1; j < half; ++j) { dp[i] += sum[j][i % j]; if (dp[i] >= mo) dp[i] -= mo; } if (a[i] >= half) { for (int j = i + a[i]; j < n; j += a[i]) { dp[j] += dp[i]; if (dp[j] >= mo) dp[j] -= mo; } } else { sum[a[i]][i % a[i]] += dp[i]; if (sum[a[i]][i % a[i]] >= mo) sum[a[i]][i % a[i]] -= mo; } } int ans = 0; for (auto& i : dp) { ans += i; if (ans >= mo) ans -= mo; } cout << ans << '\n'; return 0; }
G - Discrete Logarithm Problems (abc335 G)
题目大意
给定一个数组和一个质数,求 的数量,满足以下条件
- 存在 ,使得
解题思路
这题难在需要有数论知识。
首先因为有质数,会存在一个原根 ,使得 的值恰好取遍了 ,由费马小定理知就回到了起点。上述的幂是对取模的。
因此可以找到个 ,使得 ,对应的则对应。
除此之外,关于阶的概念:一个数的阶是一个最小的正整数,使得 ,其实就是循环节的长度,一般记为。
然后是一个简单但深刻的定理:
- 如果存在,使得,则有
如何通俗点理解呢?考虑阶的定义,,关心指数部分的话,即为 ,即。
注意到是最小的,怎么最小呢?我们要让 是 的倍数,我们将 和 质因数分解, 缺少的那些质因数就是造成不是 的倍数的原因,这缺少的部分就由 补上,那这个 就是最小的了。注意这里的就是一个数的阶。它就是 相对于 缺少的那部分质因数,所以也必定是 的因子
这里得到了另一个简单但也深刻的定理,也是最后求一个数的阶的理论依据:
对于 来说,就是 。我们知道已经是 的倍数了,那 也必定是 的倍数,但这里的不一定是最小的,因为 也带来了一些 的质因数,因此 会比 更小,它去掉了 包括的 的一些质因数,也即 ,即。
因此,如果我们求出了每个的阶,剩下的就是求一个数的倍数有多少个。
如何快速求一个数的阶呢?注意到 ,我们先假设其阶,此时必有 ,但此时可能不是最小的,因为关于 的质因数有多余的(跟上面的 的 不是最小的一个道理),我们要把多余的质因数去掉。
如何去除呢?其实只要枚举去除哪个质数,去除该质数的指数是多少即可。当去除的过多时,就不满足 ,但还能去除时,则还会满足该等式。所谓最小的,意味着任何一个更小的数,都不会满足该等式。
注意到 只有 ,质因数种类最多只有 个,每个质数的幂最大也只有 ,因此可以在的复杂度内求出一个数的阶(还有一个是快速幂)。
因此一开始先对质因数分解,然后求出每个数的阶,此时显然不能枚举阶,判断倍数关系。
注意到 只有,最多只有 个左右的质数,因此其因数个数最多只有 ,是的数量级,因为阶一定是的倍数,虽然有 个,但不同的阶的数量不超过 ,因此我们可以统计每个阶的数量,直接 枚举每个阶的数量,判断倍数累加答案即可。
注意判阶数时的快速幂可能会爆long long
。
最终的时间复杂度是
神奇的代码
#include <bits/stdc++.h> using namespace std; using LL = long long; long long qpower(__int128 a, __int128 b, long long mo) { __int128 qwq = 1; while (b) { if (b & 1) qwq = qwq * a % mo; a = a * a % mo; b >>= 1; } return qwq; } int main(void) { ios::sync_with_stdio(false); cin.tie(0); cout.tie(0); int n; LL mo; cin >> n >> mo; LL phi = mo - 1, tmp = phi; vector<LL> a(n); for (auto& i : a) cin >> i; vector<LL> prime; int half = sqrt(phi) + 1; for (int i = 2; i <= half; ++i) { if (phi % i == 0) { prime.push_back(i); while (phi % i == 0) phi /= i; } } if (phi != 1) prime.push_back(phi); phi = tmp; map<LL, int> cnt; auto order = [&](LL x) { LL m = phi; for (auto& i : prime) { while (m % i == 0 && qpower(x, m / i, mo) == 1) { m /= i; } } return m; }; for (auto& i : a) { cnt[order(i)]++; } LL ans = 0; for (auto& i : cnt) { for (auto& j : cnt) { if (i.first % j.first == 0) { ans += 1LL * i.second * j.second; } } } cout << ans << '\n'; return 0; }
本文作者:~Lanly~
本文链接:https://www.cnblogs.com/Lanly/p/17974808
版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
2017-01-19 多叉树转换二叉树