AtCoder Beginner Contest 359
A - Count Takahashi (abc359 A)
题目大意
给定个字符串,问有多少个字符串是Takahashi
解题思路
注意判断比较即可。
神奇的代码
#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 ans = 0; while (n--) { string s; cin >> s; ans += s == "Takahashi"; } cout << ans << '\n'; return 0; }
B - Couples (abc359 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; n *= 2; vector<int> a(n); for (auto& x : a) cin >> x; int ans = 0; for (int i = 1; i < n - 1; ++i) { ans += a[i - 1] == a[i + 1]; } cout << ans << '\n'; return 0; }
C - Tile Distance 2 (abc359 C)
题目大意
给定一个坐标系,有格子,如下:
给定起点和终点,问从起点到终点,要穿过多少次蓝线。
解题思路
观察上述格子,可以发现在轴移动,每移动一次,必定穿过一次蓝线。
由于每行格子交错排列的,每往上走一个,我左右可走的区间都扩大了。比如我在,我可以左边往上走到的格子,也可以右边往上走到。
这样,原本我左右走的横坐标区间是,往上走一格后,横坐标区间扩大为,往上走格,可到达的横坐标区间范围为,只要我终点的横坐标在这区间,那我就可以只花费轴移动的代价就抵达终点了。而如果不在这个区间,那就再左右移动,每移动一次,横坐标区间就变动。
容易发现这样移动一定是最优的。轴移动的蓝线穿过不可避免,然后轴的蓝线穿过已经尽可能在移动轴时避免了。
神奇的代码
#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 sx, sy, tx, ty; cin >> sx >> sy >> tx >> ty; LL dy = abs(sy - ty); LL odd = (sx & 1); LL l = sx - odd + (sy & 1) * (odd ? 1 : -1); LL r = l + 2; l -= dy, r += dy; LL ans = dy + max(0ll, l - tx + 1) / 2 + max(0ll, tx - r + 2) / 2; cout << ans << '\n'; return 0; }
D - Avoid K Palindrome (abc359 D)
题目大意
给定一个包含AB?
的字符串,将?
变成A
或B
,问有多少种情况,使得没有长度为的回文子串。
解题思路
注意
从左到右考虑每个字符,如果当前是?
,则考虑其变为A
,B
,是否出现长度为的回文串。
我们需要知道该?
前位的情况,加上该字母,就可以判断出新增的子串是不是回文串。
即设表示考虑前位字符,其中?
都已经替换成A
或B
后,且后位的字符状态为(因为只有AB
两种,可以编码成01
,用二进制压缩表示)。
然后考虑当前位的情况,如果取值为A
即,则判断状态是不是回文串,不是的话则有,否则就状态非法,不转移。因为是后个字符的状态信息,而的含义是后位,所以是把第位去掉。
同理,取值为的话,即,则判断是不是回文串,不是的话就转移,否则不转移。
可以事先预处理每个状态是否是回文串,然后当时再考虑转移的合法性。
神奇的代码
#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, k; string s; cin >> n >> k >> s; int up = (1 << k); vector<int> p(up); for (int i = 0; i < up; i++) { vector<int> bit(k); int num = i; for (int j = 0; j < k; j++) { bit[j] = (num & 1); num >>= 1; } auto rev = bit; reverse(rev.begin(), rev.end()); p[i] = rev == bit; } up = 1 << (k - 1); int mask = up - 1; vector<int> dp(up, 0); dp[0] = 1; for (int i = 0; i < n; ++i) { int chr = s[i]; vector<int> dp2(up, 0); for (int j = 0; j < up; j++) { if (chr == '?') { if (i + 1 < k || !p[j << 1]) { int nxt = (j << 1) & mask; dp2[nxt] = (dp2[nxt] + dp[j]) % mo; } if (i + 1 < k || !p[j << 1 | 1]) { int nxt = (j << 1 | 1) & mask; dp2[nxt] = (dp2[nxt] + dp[j]) % mo; } } else { if (i + 1 < k || !p[j << 1 | (chr - 'A')]) { int nxt = (j << 1 | (chr - 'A')) & mask; dp2[nxt] = (dp2[nxt] + dp[j]) % mo; } } } dp.swap(dp2); } LL ans = 0; for (int i = 0; i < up; i++) { ans = (ans + dp[i]) % mo; } cout << ans << '\n'; return 0; }
E - Water Tank (abc359 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); int n; cin >> n; vector<int> h(n + 1); for (int i = 1; i <= n; ++i) cin >> h[i]; h[0] = 1e9 + 8; vector<int> hei; hei.push_back(0); LL ans = 0; for (int i = 1; i <= n; ++i) { while (!hei.empty() && h[hei.back()] <= h[i]) { ans -= h[hei.back()] * (LL)(hei.back() - hei[hei.size() - 2]); hei.pop_back(); } ans += h[i] * (LL)(i - hei.back()); hei.push_back(i); cout << ans + 1 << " \n"[i == n]; } return 0; }
F - Tree Degree Optimization (abc359 F)
题目大意
给定个点的点权,构造一棵树,使得最小,其中表示点的度。
解题思路
由于是一棵树,则有。
对于任意满足上述条件的,都可以构造出对应的树,使得每个点的度数都是。(构造方法为,每次选择度数为1
和非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; priority_queue<pair<LL, int>, vector<pair<LL, int>>, greater<pair<LL, int>>> q; LL ans = accumulate(a.begin(), a.end(), 0ll); for (int i = 0; i < n; i++) { q.push({a[i] * 3ll, 2}); } for (int i = 0; i < n - 2; i++) { auto [x, y] = q.top(); q.pop(); ans += x; if (y < n - 1) { LL ori = x / (2 * y - 1); LL nxt = ori * (2 * y + 1); q.push({nxt, y + 1}); } } cout << ans << '\n'; return 0; }
G - Sum of Tree Distance (abc359 G)
题目大意
给定一棵树,点有点权。求,其中。表示点的距离,边权为。
解题思路
距离的最终来源是边数,考虑每条边被算入了多少次,即对答案贡献的次数。
即,其中表示边对答案贡献的次数,考虑该次数怎么算。
考虑边,将该树分成了两个连通块,如果这两个连通块各有一点,其,那么从点必定经过该边,因此需要统计每个点权,在两个连通块的出现次数,其乘积的和则是该边的贡献。
问题就变成了统计一个子树里,各个点权的出现次数,事先预处理每个点权的出现次数,对点权求和,即就是该边对答案的贡献。
由于点权是稀疏的,用map
来维护出现次数,合并儿子之间的map
,采用启发式合并
,即用数量少的合并到数量大的,这样每次合并最坏的复杂度是,而最坏的情况最多只有次(每一次最坏情况,合并后的点数会翻倍,最多翻倍次。
合并的时候,计算边贡献的式子,只有一项发生变化,可以动态维护出更新后的贡献。
最终的时间复杂度就是,一个是启发式合并,另一个是map
。
代码里的是考虑父亲边对答案的贡献。由于返回类型是,用构造会复制构造造成巨大的性能损失,用函数进行移动构造。或者返回值仅为,编译器也会优化成移动构造。
神奇的代码
#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<vector<int>> edge(n); for (int i = 0; i < n - 1; i++) { int u, v; cin >> u >> v; u--; v--; edge[u].push_back(v); edge[v].push_back(u); } vector<int> a(n); vector<int> cnt(n); for (auto& x : a) { cin >> x; --x; cnt[x]++; } LL ans = 0; auto dfs = [&](auto& dfs, int u, int fa) -> pair<map<int, int>, LL> { map<int, int> cc; LL sum = 0; for (auto v : edge[u]) { if (v == fa) continue; auto&& [son_ret, son_sum] = dfs(dfs, v, u); if (son_ret.size() > cc.size()) { swap(son_ret, cc); swap(son_sum, sum); } for (auto& [k, v] : son_ret) { sum -= 1ll * cc[k] * (cnt[k] - cc[k]); cc[k] += v; sum += 1ll * cc[k] * (cnt[k] - cc[k]); } } sum -= 1ll * cc[a[u]] * (cnt[a[u]] - cc[a[u]]); cc[a[u]]++; sum += 1ll * cc[a[u]] * (cnt[a[u]] - cc[a[u]]); ans += sum; return {move(cc), sum}; }; dfs(dfs, 0, 0); cout << ans << '\n'; return 0; }
本文作者:~Lanly~
本文链接:https://www.cnblogs.com/Lanly/p/18263628
版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步