Skip to content

Commit 4474986

Browse files
committed
add abc370 abc371
1 parent 4e5abe9 commit 4474986

File tree

4 files changed

+830
-0
lines changed

4 files changed

+830
-0
lines changed

docs/algorithm/AtCoder/abc370.md

Lines changed: 343 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,343 @@
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

Comments
 (0)