AtCoder Beginner Contest 336
A - Long Loong (abc336 A)
题目大意
给定一个数,将 long
中的o
重复次后输出。
解题思路
模拟即可。
神奇的代码
#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; cout << "L" << string(n, 'o') << "ng" << '\n'; return 0; }
B - CTZ (abc336 B)
题目大意
给定一个数,问 的二进制表示下的末尾零的数量。
解题思路
即找到最小的使得 不为零的位置。枚举即可。
或者直接用内置函数 __builtin_ctz
。(count tail zero?
神奇的代码
#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 x; cin >> x; cout << __builtin_ctz(x) << '\n'; return 0; }
C - Even Digits (abc336 C)
题目大意
定义一个数种类,每个数位都是偶数。
给定,问第 大的数。
解题思路
每一位的取值只有0,2,4,6,8
五种,其实就是一个5进制的转换,再将每个数位乘以即可。
神奇的代码
#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); LL n; cin >> n; --n; string ans; while (n) { ans += (n % 5) * 2 + '0'; n /= 5; } reverse(ans.begin(), ans.end()); if (ans.empty()) ans = "0"; cout << ans << '\n'; return 0; }
D - Pyramid (abc336 D)
题目大意
给定一个数组,求经过以下操作可以得到的最长的金字塔序列。
金字塔序列形如。
可进行的操作为:
- 将某数减1
- 将开头或末尾的数移除
解题思路
考虑这个金字塔序列的特点,如果选择中间最高点的的位置,以及 的值,那剩下的就是看从该位置左右延伸,是否都不小于对应的值。但这复杂度是 ,不大行。
注意到金字塔序列是一个对称的,可以考虑 的位置,然后看看能往左和往右延伸到多远。那最后再枚举左边的 的位置和右边 的位置, 看看它们往右和往左延伸的距离能不能覆盖这两个之间的值。
考虑如何求从 往右能延伸多远,假设能延伸到,就要要求这期间的 都满足 。朴素求法是 。但注意到一个性质,比如 能延伸到 ,那 至少也会延伸到 ,因为原本在为起点时,需要满足 , 而以为起点时需要满足的是 ,条件更松了,之前满足的也肯定满足,因此求时直接从上次的 继续往右延伸即可,而不必重新从开始求 。即双指针的求法,复杂度是。
往左和往右的求法是一样的,当求出从 往左和往右延伸的最远距离 和 后,如果枚举两个 的位置判断,那还是 不行。但容易发现这个也可以用双指针的方法枚举两个 的位置。因此时间复杂度就是 。
注意金字塔序列的长度是奇数。
神奇的代码
#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; vector<int> a(n); for (auto& x : a) cin >> x; auto solve = [&](vector<int>& a) { vector<int> pos(n); int l = 0; for (int i = 0; i < n; ++i) { while (l < i && a[i] <= i - l) { pos[l] = i - l; ++l; } } while (l < n) { pos[l] = n - l; ++l; } return pos; }; auto r = solve(a); ranges::reverse(a); auto l = solve(a); int ans = 0; ranges::reverse(l); int R = 0; for (int L = 0; L < n; ++L) { while (R < n && r[L] + l[R] >= R - L + 1) { if ((R - L + 1) & 1) ans = max(ans, (R - L + 1) / 2 + 1); ++R; } } cout << ans << '\n'; return 0; }
E - Digit Sum Divisible (abc336 E)
题目大意
给定,问 中,能被自己的数位和整除的数的个数。
解题思路
有 ,显然不能枚举求。
对能整除的数的计数,可以从高位到低位一位一位地考虑,只要知道高位对除数的取余结果,就可以决定低位的取值,进而知道取余的结果变为多少,最后看是否为 来判断是不是整除。即一个经典的数位的形式。
但棘手的是 除数不是固定的,它是由一个数的数位和决定,在一般的数位过程中,其数位和会变化,即除数会变化,这就导致余数不能求得,进而最后的能否整除就难以判断。
但注意到数位和的范围不大,它只有,可以考虑枚举数位和, 那剩下的就是求:数位和是,且对 取余结果为的值的数的个数。
这两个都是常规条件,数位即可解决。
时间复杂度为
数位就考虑递归的形式,从高位到低位考虑每一位的取值,记录中间状态有:当前考虑的位数,取值是否受的限制,高位的取余结果,已经填的高位的数位和(下述代码里是低位需要的数位和,反了过来)。然后记忆化一下。记忆化的数组可以理解成:已经填了的高位,其取余结果为 ,数位和为 时,低位的全部填法中,满足题目要求(取余为和数位和)的填法数量。
有时候 数组会加一维 ,这个可有可无, 没有的话就这部分会重复求解,但因为数量不多,所以不会太费时间。有的话就可能会更快,但会费点空间。
神奇的代码
#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); LL n; cin >> n; vector<int> s; while (n) { s.push_back(n % 10); n /= 10; } ranges::reverse(s); LL ans = 0; int maxsum = 9 * min(14ul, s.size()); vector<vector<vector<LL>>> dp( s.size(), vector<vector<LL>>(maxsum + 1, vector<LL>(maxsum + 1, -1))); auto solve = [&](auto self, int pos, int limit, int mod, int sum, int div) -> LL { if (sum < 0) return 0ll; if (pos == s.size()) return LL(mod == 0 && sum == 0); if (!limit && dp[pos][mod][sum] != -1) return dp[pos][mod][sum]; int up = (limit ? s[pos] : 9); LL ret = 0; for (int i = 0; i <= up; ++i) { ret += self(self, pos + 1, limit && i == up, (mod * 10 + i) % div, sum - i, div); } if (!limit) dp[pos][mod][sum] = ret; return ret; }; for (int i = 1; i <= maxsum; ++i) { for (auto& i : dp) for (auto& j : i) ranges::fill(j, -1); auto ret = solve(solve, 0, 1, 0, i, i); ans += ret; } cout << ans << '\n'; return 0; }
F - Rotation Puzzle (abc336 F)
题目大意
给定一个的矩阵,的恰好填了一次。
问能否经过不超过 次操作,使得矩阵变成一个正常的矩阵,即上的数是 。若可,输出最小的操作次数。
操作为:选定一个 的子矩阵,旋转度。
解题思路
注意到只有个子矩阵,那每次操作其实只有 种选择,直接的复杂度是 ,即 。
注意到这是个非常特别的复杂度,它的一半即 是可做的。
注意到起始局面和终点局面都是已知的,且操作都是可逆的。因此可以从起点和终点分别10步, 然后看搜得的局面是否有交集,取步数和最小值即可。
神奇的代码
#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 h, w; cin >> h >> w; vector<vector<int>> a(h, vector<int>(w)); for (auto& i : a) for (auto& j : i) cin >> j; vector<vector<int>> b(h, vector<int>(w)); for (int i = 0, cnt = 1; i < h; ++i) for (int j = 0; j < w; ++j) { b[i][j] = cnt; cnt++; } auto tr = [&](vector<vector<int>>& st, int x, int y) { auto ret = st; for (int i = x; i < x + h - 1; ++i) for (int j = y; j < y + w - 1; ++j) { ret[i][j] = st[h - 2 - (i - x) + x][w - 2 - (j - y) + y]; } return ret; }; auto BFS = [&](vector<vector<int>>& st, int up) { map<vector<vector<int>>, int> dis; queue<vector<vector<int>>> team; team.push(st); dis[st] = 0; while (!team.empty()) { auto u = team.front(); team.pop(); if (dis[u] == up) break; for (int i = 0; i <= 1; ++i) for (int j = 0; j <= 1; ++j) { auto v = tr(u, i, j); if (dis.count(v)) continue; dis[v] = dis[u] + 1; team.push(v); } } return dis; }; auto sol1 = BFS(a, 10); auto sol2 = BFS(b, 10); int ans = inf; for (auto& [tu, dis] : sol1) { if (sol2.count(tu)) { ans = min(ans, dis + sol2[tu]); } } if (ans == inf) ans = -1; cout << ans << '\n'; return 0; }
G - 16 Integers (abc336 G)
题目大意
给定个数 。
设为这 个数的和。
问一个长度为 的数组的数量,满足以下条件:
- 对于每一个数 ,数组中恰好有 个下标满足:
解题思路
感觉是个神仙题,以后再来探索吧
神奇的代码
本文作者:~Lanly~
本文链接:https://www.cnblogs.com/Lanly/p/17976539
版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
2020-01-20 Codeforces Round #614 (Div. 2)