AtCoder Beginner Contest 307
A - Weekly Records (abc307 A)
题目大意
给定周每天的散步量,求每周七天的散步量的和。
解题思路
累计求和即可。
神奇的代码
#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; while(n--){ int sum = 0; for(int i = 0; i < 7; ++ i){ int a; cin >> a; sum += a; } cout << sum << ' '; } return 0; }
B - racecar (abc307 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; vector<string> s(n); for(auto &i : s) cin >> i; auto palind = [&](int x, int y){ string l = s[x] + s[y]; string r = l; reverse(r.begin(), r.end()); return l == r; }; auto ok = [&](){ for(int i = 0; i < n; ++ i) for(int j = 0; j < n; ++ j){ if (i != j && palind(i, j)) return true; } return false; }; if (ok()) cout << "Yes" << '\n'; else cout << "No" << '\n'; return 0; }
C - Ideal Sheet (abc307 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); array<int, 3> h, w; array<vector<string>, 3> d; array<int, 3> blk{}; for(int i = 0; i < 3; ++ i){ cin >> h[i] >> w[i]; d[i].resize(h[i]); for(int j = 0; j < h[i]; ++ j){ cin >> d[i][j]; blk[i] += count(d[i][j].begin(), d[i][j].end(), '#'); } } auto check = [&](int x1, int y1, int x2, int y2){ int tot = 0; for(int i = 0; i < h[2]; ++ i) for(int j = 0; j < w[2]; ++ j){ int target = (d[2][i][j] == '#'); int blk1 = (i >= x1 && j >= y1 && i - x1 < h[0] && j - y1 < w[0] && d[0][i - x1][j - y1] == '#'); int blk2 = (i >= x2 && j >= y2 && i - x2 < h[1] && j - y2 < w[1] && d[1][i - x2][j - y2] == '#'); tot += blk1 + blk2; if (target != (blk1 || blk2)) return false; } return (tot == blk[0] + blk[1]); }; auto ok = [&](){ for(int i = -10; i <= 10; ++ i) for(int j = -10; j <= 10; ++ j) for(int k = -10; k <= 10; ++ k) for(int l = -10; l <= 10; ++ l){ if (check(i, j, k, l)) return true; } return false; }; if (ok()) cout << "Yes" << '\n'; else cout << "No" << '\n'; return 0; }
D - Mismatched Parentheses (abc307 D)
题目大意
给定一个包含小写字母、(
、)
的字符串。每次选择一个开头是(
,结尾是)
,且中间不包含这两个字符的子串,移除该子串。
问最终剩余的字符串是怎样的。
解题思路
每次移除的其实是一个最里面的合法的()
,假设有(()())
,每次移除的都是最里端的一对()
,因此反复这么操作,合法的括号序列都会被移除掉。
因此我们对原字符串的每个(
统计与之对应的)
,这样每个()
之间的内容都会被移除。
根据这个对应关系就会跳过一些字符串(被移除了),剩下的就是没被移除的了。
神奇的代码
#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; string s; cin >> n >> s; vector<int> r(n, -1); stack<int> pos; for(int i = 0; i < n; ++ i){ if (s[i] == '(') pos.push(i); else if (s[i] == ')' && !pos.empty()){ r[pos.top()] = i; pos.pop(); } } for(int i = 0; i < n; ++ i){ if (r[i] != -1){ i = r[i]; }else { cout << s[i]; } } cout << '\n'; return 0; }
E - Distinct Adjacent (abc307 E)
题目大意
给定一个个数的环形数组,每个数的取值为。
问有多少种取值情况,满足任意相邻两个数不相同。
解题思路
如果不是环形,答案很明显是。但由于环形,最后一位的取值难以确定是还是 ,这取决于倒数第二位是否和第一位相同。
如何解决呢?其实问题的核心上面已经指明了:倒数第二位是否和第一位相同
。
- 如果相同,则最后一位可取种情况。
- 如果不相同,则最后一位可取 种情况。
由于每个数都是对称的,我们假设第一位填的数是 。然后设 表示前 位,相邻两数不同(不考虑首尾),且最后一位与首位数字不同
/相同
的方案数。转移就看当前位是否与首位相同。
因为上述我们假定了首位取,事实上首位取什么值情况都一样,而它有 种取法,因此最终答案就是。
神奇的代码
#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, m; cin >> n >> m; array<int, 2> dp{0, 1}; for(int i = 1; i < n; ++ i){ array<int, 2> dp2{}; dp2[0] = 1ll * dp[0] * (m - 2) % mo + 1ll * dp[1] * (m - 1) % mo; if (dp2[0] >= mo) dp2[0] -= mo; dp2[1] = dp[0]; dp.swap(dp2); } cout << 1ll * dp[0] * m % mo << '\n'; return 0; }
F - Virus 2 (abc307 F)
题目大意
给定一张无向图,边有边权。
初始有个点感染病毒。
持续 天。对于第 天,所有与已感染病毒的点距离不超过的点都会被感染病毒。
问每个点第一次被感染病毒的天数。
解题思路
因为有多个起点,我们建立一个超级源点,它与所有已感染病毒的点连一条边权为 的无向边。
考虑第一天,我们从超级源点 开始 ,所有距离不超过 的点都会被感染病毒。然后新感染的点又会与超级源点连一条边权为 的边。很显然当跑到距离大于时我们就无需继续跑 了。
之后第二天重复跑,依次类推,时间复杂度是 ,是无法通过的。
究其原因,主要是舍弃了之前信息,重复操作了:当一个新的点被感染时,它会影响与其相邻点
的最短路距离,这个相邻点
只包含未被感染的点
——因为已被感染的点意味着与超级源点有条 边权的边,从出发的最短路一定优于从的。因此对于已被感染的点
与未被感染的点
的边的情况是可以继承上一次,即复用前一天的的优先队列,而不用重新求,因为没有变化。
换句话说,假设第一天有个新感染的点 ,我们只需要更新这些新感染的点
的未被感染的相邻点
的最短距离,将其加入到 的优先队列里。第二天就继续从这个优先队列开始。
这样,每条边最多只会被考虑两次——一次是正常的 ,另一次是当其中一个端点被感染病毒后重新考虑。
因此总的时间复杂度是
神奇的代码
#include <bits/stdc++.h> using namespace std; using LL = long long; const LL inf = 1e18; int main(void) { ios::sync_with_stdio(false); cin.tie(0); cout.tie(0); int n, m; cin >> n >> m; vector<vector<array<int, 2>>> edge(n); for(int i = 0; i < m; ++ i){ int u, v, w; cin >> u >> v >> w; -- u, -- v; edge[u].push_back({v, w}); edge[v].push_back({u, w}); } vector<LL> dis(n, inf); vector<int> ans(n, -1); priority_queue<pair<LL, int>> team; int k; cin >> k; for(int i = 0; i < k; ++ i){ int u; cin >> u; -- u; dis[u] = 0; ans[u] = 0; for(auto &[v, w] : edge[u]){ if (dis[v] > dis[u] + w){ dis[v] = dis[u] + w; team.push({-dis[v], v}); } } } int d; cin >> d; for(int i = 0; i < d; ++ i){ int x; cin >> x; vector<int> infected; while(!team.empty()){ auto [d, u] = team.top(); if (dis[u] > x) break; team.pop(); if (dis[u] != -d || ans[u] != -1) continue; ans[u] = i + 1; infected.push_back(u); for(auto &[v, w] : edge[u]){ if (dis[v] > dis[u] + w){ dis[v] = dis[u] + w; team.push({-dis[v], v}); } } } for(auto &u : infected) for(auto &[v, w]: edge[u]){ if (dis[v] > w){ dis[v] = w; team.push({-dis[v], v}); } } } for(int i = 0; i < n; ++ i) cout << ans[i] << "\n"; return 0; }
G - Approximate Equalization (abc307 G)
题目大意
给定一个有个数的数组,可进行两种操作:
- 选定一个,令
- 选定一个,令
问最少进行的操作次数,使得数组的极差不超过。
解题思路
观察操作,可以发现每次操作后,这个数组的总和是不变的。(其实可以看成有个小球, 个隔板,每次操作其实就是移动一个隔板到相邻位置)
由于极差不超过,那么最终每个数要么是 ,要么是 ,且至多有个数是后者。问题就是让哪些数是后者,哪些数是前者。
首先明确一点,如果给定最终的数组,求将该数组变成最终数组的操作次数,其最优方案是可以在内求出来。就是依次对每个数,将其变为目标值,需要从后一个数 借
多少次(操作二),还是给
多少次(操作一),然后这个借
和给
的影响是持续的。
现在问题变成了让哪些数是,哪些是,这是个分配问题,设 表示前 个数,还有 个 的未分配的最小操作数。需要第二维状态一方面是要确保有个 ,另一方面是需要知道由于前面的数的操作,当前数变成了多少。
对于当前数,知道了 ,我们就知道前面有多少个数是 ,多少个数是,进而知道,在上述求解操作的影响下, 变成了多少,然后就可以进一步求解 变成目标数所需要的操作次数了。
注意如果是负数,则可以让,这样就变回正数了。
神奇的代码
#include <bits/stdc++.h> using namespace std; using LL = long long; const LL inf = 1e18; int main(void) { ios::sync_with_stdio(false); cin.tie(0); cout.tie(0); int n; cin >> n; vector<LL> a(n); for(auto &i : a) cin >> i; LL sum = accumulate(a.begin(), a.end(), 0ll); LL div = sum / n, mod = sum % n; if (mod < 0){ mod = n + mod; div --; } vector<LL> dp(mod + 1, inf); dp[mod] = 0; LL presum = 0; LL tot = 0; for(auto &x : a){ vector<LL> dp2(mod + 1, inf); for(int i = 0; i <= mod; ++ i){ LL cur = x - (tot + mod - i - presum); dp2[i] = min(dp2[i], dp[i] + abs(cur - div)); if (i) dp2[i - 1] = min(dp2[i - 1], dp[i] + abs(cur - div - 1)); } presum += x; tot += div; dp.swap(dp2); } cout << dp[0] << '\n'; return 0; }
Ex - Marquee (abc307 Ex)
题目大意
<++>
解题思路
<++>
神奇的代码
本文作者:~Lanly~
本文链接:https://www.cnblogs.com/Lanly/p/17501795.html
版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步