|
| 1 | +## [A - Raise Both Hands](https://atcoder.jp/contests/abc370/tasks/abc370_a) |
| 2 | + |
| 3 | +???+ Abstract "题目大意" |
| 4 | + |
| 5 | + 给你两个数字 $L$ 和 $R$,如果 $L=R$ 输出 `Invalid`,否则如果 $L = 1$ 输出 `Yes`,否则输出 `No`。 |
| 6 | + |
| 7 | +??? Success "参考代码" |
| 8 | + |
| 9 | + === "C++" |
| 10 | + |
| 11 | + ```c++ |
| 12 | + #include <iostream> |
| 13 | + |
| 14 | + using namespace std; |
| 15 | + |
| 16 | + int main() |
| 17 | + { |
| 18 | + int l, r; |
| 19 | + cin >> l >> r; |
| 20 | + if(l == r) |
| 21 | + cout << "Invalid" << endl; |
| 22 | + else |
| 23 | + cout << (l ? "Yes" : "No") << endl; |
| 24 | + return 0; |
| 25 | + } |
| 26 | + ``` |
| 27 | + |
| 28 | +--- |
| 29 | + |
| 30 | +## [B - Binary Alchemy](https://atcoder.jp/contests/abc370/tasks/abc370_b) |
| 31 | + |
| 32 | +???+ Abstract "题目大意" |
| 33 | + |
| 34 | + 给你 $N$ 个数字 $1, 2, \cdots, N$。数字 $i$ 和数字 $j$ 可以合并,合并时如果 $i \ge j$ 则合并的结果为 $A_{i, j}$,否则为 $A_{j, i}$,其中 $1 \le A_{i, j} \le N$。初始的时候,给你一个数字 $1$,依次和数字 $1, 2, \cdots,N$ 合并,问:最终得到的数字是? |
| 35 | + |
| 36 | +??? Success "参考代码" |
| 37 | + |
| 38 | + === "C++" |
| 39 | + |
| 40 | + ```c++ |
| 41 | + #include <iostream> |
| 42 | + #include <vector> |
| 43 | + |
| 44 | + using namespace std; |
| 45 | + |
| 46 | + int main() |
| 47 | + { |
| 48 | + int n; |
| 49 | + cin >> n; |
| 50 | + vector<vector<int>> a(n+1, vector<int>(n+1)); |
| 51 | + for(int i = 1; i <= n; i++) |
| 52 | + for(int j = 1; j <= i; j++) |
| 53 | + cin >> a[i][j]; |
| 54 | + int i = 1; |
| 55 | + for(int j = 1; j <= n; j++) |
| 56 | + i = i >= j ? a[i][j] : a[j][i]; |
| 57 | + cout << i << endl; |
| 58 | + return 0; |
| 59 | + } |
| 60 | + ``` |
| 61 | + |
| 62 | +--- |
| 63 | + |
| 64 | +## [C - Word Ladder](https://atcoder.jp/contests/abc370/tasks/abc370_c) |
| 65 | + |
| 66 | +???+ Abstract "题目大意" |
| 67 | + |
| 68 | + 给你两个长度相同的字符串 $S$ 和 $T$。设 $X$ 表示一个字符串数组,初始时为空,然后,你可以执行以下操作直到 $S$ 等于 $T$。 |
| 69 | + |
| 70 | + - 每次选取 $S$ 中的一个字符,将其替换成任意字符,然后将当前的 $S$ 加入到 $X$ 的末尾。 |
| 71 | + |
| 72 | + 所有操作结束时,你首先需要输出最少操作多少次才能将 $S$ 变成 $T$。然后按顺序输出数组 $X$ 的内容,如果有多个答案符合要求,输出字典序最小的答案。 |
| 73 | + |
| 74 | +??? Note "解题思路" |
| 75 | + |
| 76 | + 从左到右替换字典序减小的字符,从右到左替换字典序增大的字符。 |
| 77 | + |
| 78 | +??? Success "参考代码" |
| 79 | + |
| 80 | + === "C++" |
| 81 | + |
| 82 | + ```c++ |
| 83 | + #include <algorithm> |
| 84 | + #include <iostream> |
| 85 | + #include <string> |
| 86 | + #include <vector> |
| 87 | + |
| 88 | + using namespace std; |
| 89 | + |
| 90 | + int main() |
| 91 | + { |
| 92 | + string s, t; |
| 93 | + cin >> s >> t; |
| 94 | + int n = s.size(); |
| 95 | + int cnt = 0; |
| 96 | + vector<int> less, greater; |
| 97 | + for(int i = 0; i < n; i++) |
| 98 | + { |
| 99 | + if(s[i] == t[i]) |
| 100 | + continue; |
| 101 | + cnt++; |
| 102 | + s[i] < t[i] ? less.push_back(i) : greater.push_back(i); |
| 103 | + } |
| 104 | + cout << cnt << endl; |
| 105 | + for(auto i : greater) |
| 106 | + { |
| 107 | + s[i] = t[i]; |
| 108 | + cout << s << endl; |
| 109 | + } |
| 110 | + reverse(less.begin(), less.end()); |
| 111 | + for(auto i : less) |
| 112 | + { |
| 113 | + s[i] = t[i]; |
| 114 | + cout << s << endl; |
| 115 | + } |
| 116 | + return 0; |
| 117 | + } |
| 118 | + ``` |
| 119 | + |
| 120 | +--- |
| 121 | + |
| 122 | +## [D - Cross Explosion](https://atcoder.jp/contests/abc370/tasks/abc370_d) |
| 123 | + |
| 124 | +???+ Abstract "题目大意" |
| 125 | + |
| 126 | + 有一个 $H \times W(1 \le H \times W \le 4 \times 10 ^ 5)$ 的网格,初始的时候,每个格子上都有一堵墙。有 $Q(1 \le Q \le 2 \times 10 ^5)$ 个炸弹依次放下,第 $i$ 个炸弹会放置在 $(R_1, C_i)$ 位置。当炸弹放下时,如果 $(R_i, C_i)$ 是墙,则这枚炸弹仅仅摧毁 $(R_i, C_i)$ 这一堵墙,如果 $(R_i, C_i)$ 是墙,炸弹会沿着上下左右四个方向传播,并炸毁距离 $(R_i, C_i)$ 四个方向最近的各一堵墙。问:在 $Q$ 个炸弹放置完成后,网格中还剩下多少堵墙? |
| 127 | + |
| 128 | +??? Note "解题思路" |
| 129 | + |
| 130 | + 用 $H$ 个 `set` 记录每一行有哪些墙的纵坐标,$W$ 个 `set` 记录每一列有哪些墙的横坐标。处理炸弹的时候同时更新即可。 |
| 131 | + |
| 132 | +??? Success "参考代码" |
| 133 | + |
| 134 | + === "C++" |
| 135 | + |
| 136 | + ```c++ |
| 137 | + #include <iostream> |
| 138 | + #include <set> |
| 139 | + #include <vector> |
| 140 | + |
| 141 | + using namespace std; |
| 142 | + |
| 143 | + int main() |
| 144 | + { |
| 145 | + int h, w, q; |
| 146 | + cin >> h >> w >> q; |
| 147 | + vector<set<int>> row(h + 1), col(w + 1); |
| 148 | + for (int r = 1; r <= h; r++) |
| 149 | + for (int c = 1; c <= w; c++) |
| 150 | + row[r].insert(c); |
| 151 | + for (int c = 1; c <= w; c++) |
| 152 | + for (int r = 1; r <= h; r++) |
| 153 | + col[c].insert(r); |
| 154 | + while (q--) |
| 155 | + { |
| 156 | + int r, c; |
| 157 | + cin >> r >> c; |
| 158 | + auto it = row[r].lower_bound(c); |
| 159 | + if (it != row[r].end() && *it == c) |
| 160 | + { |
| 161 | + row[r].erase(it); |
| 162 | + col[c].erase(r); |
| 163 | + } |
| 164 | + else |
| 165 | + { |
| 166 | + if (it != row[r].begin()) |
| 167 | + { |
| 168 | + --it; |
| 169 | + col[*it].erase(r); |
| 170 | + it = row[r].erase(it); |
| 171 | + } |
| 172 | + if (it != row[r].end()) |
| 173 | + { |
| 174 | + col[*it].erase(r); |
| 175 | + row[r].erase(it); |
| 176 | + } |
| 177 | + it = col[c].lower_bound(r); |
| 178 | + if (it != col[c].begin()) |
| 179 | + { |
| 180 | + --it; |
| 181 | + row[*it].erase(c); |
| 182 | + it = col[c].erase(it); |
| 183 | + } |
| 184 | + if (it != col[c].end()) |
| 185 | + { |
| 186 | + row[*it].erase(c); |
| 187 | + col[c].erase(it); |
| 188 | + } |
| 189 | + } |
| 190 | + } |
| 191 | + int ans = 0; |
| 192 | + for(int r = 1; r <= h; r++) |
| 193 | + ans += row[r].size(); |
| 194 | + cout << ans << endl; |
| 195 | + return 0; |
| 196 | + } |
| 197 | + ``` |
| 198 | + |
| 199 | +--- |
| 200 | + |
| 201 | +## [E - Avoid K Partition](https://atcoder.jp/contests/abc370/tasks/abc370_e) |
| 202 | + |
| 203 | +???+ Abstract "题目大意" |
| 204 | + |
| 205 | + 给你一个长度为 $N(1 \le N \le 2 \times 10^5)$ 的数组 $A = (A_1, A_2, \cdots, A_N)$ 和一个整数 $K(-10^{15} \le K \le 10^{15})$,你可以将 $A$ 切割成任意段连续的子数组。 问:有多少种切割的方案使得任意一个子数组的数字之和不等于 $K$ ?答案对 $998244353$ 取模。 |
| 206 | + |
| 207 | +??? Note "解题思路" |
| 208 | + |
| 209 | + 设 $s[i]$ 表示前缀和,$dp[i]$ 表示前 $i$ 个数字进行切分所得到的合法方案数。转移时,考虑将 $A_i$ 和之前的哪些数字合并,即 $dp[i] = \sum\limits_{0\le j < i \land s[i]-s[j] \neq K}dp[j]$,这样转移是 $O(n)$ 的,总的时间复杂度就是 $O(n^2)$。 |
| 210 | + |
| 211 | + 但是我们只需要减去满足 $s[j] = s[i] - K$ 对应的 $dp[j]$ 就可以,所以只需要把对应的 $s[i]$ 放到哈希表里作为 key,$dp[i]$ 作为值,然后用一个变量存储 $dp$ 数组的前缀和即可。转移的时候,求出 $s[i]-K$ 的键值对,用前缀和减去值即可,这样转移是 $O(1)$ 的,总的时间复杂度就是 $O(n)$。 |
| 212 | + |
| 213 | +??? Success "参考代码" |
| 214 | + |
| 215 | + === "C++" |
| 216 | + |
| 217 | + ```c++ |
| 218 | + #include <iostream> |
| 219 | + #include <unordered_map> |
| 220 | + |
| 221 | + using namespace std; |
| 222 | + using LL = long long; |
| 223 | + |
| 224 | + const LL MOD = 998244353; |
| 225 | + |
| 226 | + int main() |
| 227 | + { |
| 228 | + LL n, k; |
| 229 | + cin >> n >> k; |
| 230 | + unordered_map<LL, LL> g; |
| 231 | + g[0] = 1; |
| 232 | + LL dp_prefix = 1; |
| 233 | + LL s = 0; |
| 234 | + LL dp = 1; |
| 235 | + for (int r = 1; r <= n; r++) |
| 236 | + { |
| 237 | + LL a; |
| 238 | + cin >> a; |
| 239 | + s += a; |
| 240 | + auto it = g.find(s - k); |
| 241 | + LL sub = it == g.end() ? 0 : it->second; |
| 242 | + dp = (dp_prefix - sub + MOD) % MOD; |
| 243 | + dp_prefix = (dp_prefix + dp) % MOD; |
| 244 | + g[s] = (g[s] + dp) % MOD; |
| 245 | + } |
| 246 | + cout << dp << endl; |
| 247 | + return 0; |
| 248 | + } |
| 249 | + ``` |
| 250 | + |
| 251 | +--- |
| 252 | + |
| 253 | +## [F - Cake Division](https://atcoder.jp/contests/abc370/tasks/abc370_f) |
| 254 | + |
| 255 | +???+ Abstract "题目大意" |
| 256 | + |
| 257 | + 有 $N(N \le 2 \times 10 ^ 5)$ 块蛋糕,这 $N$ 块蛋糕是环形的,按照顺时针的顺序,第 $i$ 块蛋糕的重量是 $A_i$,第 $N$ 块蛋糕的下一个蛋糕是第 $1$ 块蛋糕。每两块蛋糕之间都有一条线,第 $i$ 条线指的是第 $i$ 块蛋糕和第 $i+1$ 块蛋糕之间的线。 |
| 258 | + |
| 259 | + 你有 $K(2 \le K \le N)$ 个朋友,你打算将 $N$ 块蛋糕相邻的部分组合成 $K$ 份,分发给你的朋友们。每个朋友至少应该分到一块蛋糕。设第 $i$ 个朋友收到的蛋糕为 $A_xA_{x+1}\cdots A_y$,这些蛋糕的质量和为 $w_i = A_x + A_{x+1} + \cdots + A_y$。设 $w_{\min} = \min(w_1, w_2, \cdots, w_K)$ ,即 $K$ 个朋友中蛋糕重量之和的最小值。问:$w_{\min}$ 的最大值是什么?在所有能让 $w_{\min}$ 取得最大值的方案中,有多少条相邻蛋糕之间的连线无论如何不会切开(如果蛋糕 $i$ 和蛋糕 $i+1$ 分给了两个不同的人,则第 $i$ 条线会被切开) |
| 260 | + |
| 261 | +??? Note "解题思路" |
| 262 | + |
| 263 | + > 非常绕的题,题解也很绕。。我可能写的也不太好。。 |
| 264 | + |
| 265 | + 要最大化最小值,显然是要二分答案。考虑怎么 check。按照环形的处理方法,将 $A$ 数组扩展成两倍长,然后处理出前缀和。当我们二分到某个值 $x$ 时,等价于 check 前缀和数组中是否存在 $K$ 对 $(l_i, r_i)$ 满足 $s[r_i]-s[l_i] \ge x$,且 $l_1 \le r_1 = l_2 \le r_2 = \cdots = l_K \le r_K$ 且 $r_K - l_1 \le N$。 |
| 266 | + |
| 267 | + 在 check 的时候,处理出一个 $ne$ 数组,$ne[i]$ 表示大于 $i$ 且第一个满足 $s[ne[i]] - s[i] \ge x$ 的位置。这样的话从第一刀开始 dfs ,同时用一个栈记录下 $l_i$ 的值,当栈的大小达到 $K$ 时,再判断 $r_K - l_1 \le N$ 条件是否满足即可,只要满足这个条件,就可以判定 check 成功。下面考虑第二个问题,求出有多少条线永远不会被切开。 |
| 268 | + |
| 269 | + 我们可以发现,对于一种切分方案(需要切开 $K$ 条线),实际上有 $K$ 条线是一定会被切开的。由于我们将原数组扩展成了 $2$ 倍长度来处理环形,所以在 dfs 的时候,只需要从所有线开始枚举,每当栈的大小大于等于 $K$ 时,就把之前的 $K$ 层的 $(l_i, r_i)$ 找出来,如果满足条件,则就把 $l_i$ 一定会被某个方案切开,所以在最后返回值的时候,只需要用 $N$ 减去会被切开的线,就是第二问的答案。 |
| 270 | + |
| 271 | + 这样还有个问题,我们需要用 dfs 的方式遍历所有的路线,也就是点 $1$ 到点 $N$ 全部都要执行依次 dfs,这样会重复很多路线。有两个解决办法: |
| 272 | + |
| 273 | + - 扩展成二维的 $ne$ 数组,第二维利用倍增的方式,这样无需 dfs 遍历,可以在 $\log K$ 的时间内直接求出一个节点访问 $K$ 次之后的后继。 |
| 274 | + - 注意到当我们正序抵达某个点 $i$ 的时候,其实后继的所有点 $ne[i]$、$ne[ne[ui]]$、$......$ 都是确定好的,所以为了避免这种重复的访问,一种解决办法就是倒过来查。这个思路其实和反向建图 dijkstra 算法求出所有点到某个固定终点的最短路是一个原理,反向遍历保证了从后往前的路径都固定下来,这种遍历方式的时间复杂度是 $O(n+m)$ 的。只需要把 $ne$ 数组的构建方式替换成 $last$,从后往前 dfs 就可以了。题解用的这个方法。 |
| 275 | + |
| 276 | +??? Success "参考代码" |
| 277 | + |
| 278 | + === "C++" |
| 279 | + |
| 280 | + ```c++ |
| 281 | + #include <cmath> |
| 282 | + #include <iostream> |
| 283 | + #include <vector> |
| 284 | + |
| 285 | + using namespace std; |
| 286 | + |
| 287 | + int main() |
| 288 | + { |
| 289 | + int n, k; |
| 290 | + cin >> n >> k; |
| 291 | + vector<int> s(2 * n + 1); |
| 292 | + for (int i = 1; i <= n; i++) |
| 293 | + { |
| 294 | + cin >> s[i]; |
| 295 | + s[i + n] = s[i]; |
| 296 | + s[i] += s[i - 1]; |
| 297 | + } |
| 298 | + for (int i = n + 1; i <= 2 * n; i++) |
| 299 | + s[i] += s[i - 1]; |
| 300 | + |
| 301 | + vector<vector<int>> last(2 * n + 1); |
| 302 | + auto check = [&s, &last, n, k](int mid) -> int |
| 303 | + { |
| 304 | + last.assign(2 * n + 1, {}); |
| 305 | + for (int i = 0, j = 0; i < 2 * n; i++) |
| 306 | + { |
| 307 | + while (j < 2 * n && s[j] - s[i] < mid) |
| 308 | + j++; |
| 309 | + last[j].push_back(i); |
| 310 | + } |
| 311 | + int ans = 0; |
| 312 | + vector<int> st; |
| 313 | + auto dfs = [&last, &st, &ans, n, k](auto &self, int j) -> void |
| 314 | + { |
| 315 | + if (j < n && (int)st.size() >= k && st[st.size() - k] - j <= n) |
| 316 | + ans++; |
| 317 | + st.push_back(j); |
| 318 | + for (auto i : last[j]) |
| 319 | + self(self, i); |
| 320 | + st.pop_back(); |
| 321 | + }; |
| 322 | + dfs(dfs, 2 * n); |
| 323 | + return ans; |
| 324 | + }; |
| 325 | + int l = 1, r = s[n] / k, line = 0; |
| 326 | + while (l < r) |
| 327 | + { |
| 328 | + int mid = (l + r + 1) / 2; |
| 329 | + int ans = check(mid); |
| 330 | + if (ans == 0) |
| 331 | + r = mid - 1; |
| 332 | + else |
| 333 | + { |
| 334 | + l = mid; |
| 335 | + line = n - ans; |
| 336 | + } |
| 337 | + } |
| 338 | + |
| 339 | + cout << l << ' ' << line << endl; |
| 340 | + return 0; |
| 341 | + } |
| 342 | + ``` |
| 343 | + |
0 commit comments