Skip to content

Commit dc59f75

Browse files
committed
add abc378 abc379
1 parent d9a489b commit dc59f75

File tree

4 files changed

+904
-0
lines changed

4 files changed

+904
-0
lines changed

docs/algorithm/AtCoder/abc378.md

Lines changed: 384 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,384 @@
1+
## [A - Pairing](https://atcoder.jp/contests/abc378/tasks/abc378_a)
2+
3+
???+ Abstract "题目大意"
4+
5+
给你 $4$ 个球,第 $i$ 个球的颜色是 $A_i$。你可以执行以下操作任意次:
6+
7+
- 选择两个颜色相同的球,将他们丢弃。
8+
9+
问:最多能执行几次操作?
10+
11+
??? Success "参考代码"
12+
13+
=== "C++"
14+
15+
```c++
16+
#include <iostream>
17+
#include <unordered_set>
18+
19+
using namespace std;
20+
21+
int main()
22+
{
23+
unordered_set<int> set;
24+
int ans = 0;
25+
for(int i = 0; i < 4; i++)
26+
{
27+
int x;
28+
cin >> x;
29+
auto it = set.find(x);
30+
if(it != set.end())
31+
{
32+
ans++;
33+
set.erase(it);
34+
}
35+
else
36+
set.insert(x);
37+
}
38+
cout << ans << endl;
39+
return 0;
40+
}
41+
```
42+
43+
---
44+
45+
## [B - Garbage Collection](https://atcoder.jp/contests/abc378/tasks/abc378_b)
46+
47+
???+ Abstract "题目大意"
48+
49+
某个城市有 $N(1 \le N \le 100)$ 种类型的垃圾,每一种类型的垃圾都有固定的回收时间。第 $i$ 种类型的垃圾的回收时间由 $q_i$ 和 $r_i$ 两个数字描述,表示会在第 $r_i, r_i + q_i, r_i + 2q_i, r_i + 3q_i, ...$ 天的 **晚上** 回收垃圾。其中 $0 \le r_i < q_i \le 10 ^ 9$
50+
51+
你需要回答 $Q(1 \le Q \le 100)$ 个询问,每个询问包括两个数字 $t$ 和 $d$,你需要回答:如果在第 $d$ 天的 **白天** 产生了类型为 $t$ 的垃圾,则这个垃圾下一次被回收是在第几天?注意:因为垃圾回收总是在晚上,所有有可能当天的垃圾可以被当天回收。
52+
53+
??? Note "解题思路"
54+
55+
求出 $k = \lfloor \frac{d}{q_t} \rfloor$,$x = d \bmod q_t$,如果 $x \le r_t$,则答案就是 $r_t + kq_t$。否则答案就是 $r_t + (k+1)q_t$。
56+
57+
??? Success "参考代码"
58+
59+
=== "C++"
60+
61+
```c++
62+
#include <iostream>
63+
#include <vector>
64+
65+
using namespace std;
66+
67+
int main()
68+
{
69+
int n, m;
70+
cin >> n;
71+
vector<int> q(n+1), r(n+1);
72+
for(int i = 1; i <= n; i++)
73+
cin >> q[i] >> r[i];
74+
cin >> m;
75+
while(m--)
76+
{
77+
int t, d;
78+
cin >> t >> d;
79+
int x = d % q[t], k = d / q[t];
80+
if(x > r[t])
81+
k++;
82+
cout << r[t] + k * q[t] << endl;
83+
}
84+
return 0;
85+
}
86+
```
87+
88+
---
89+
90+
## [C - Repeating](https://atcoder.jp/contests/abc378/tasks/abc378_c)
91+
92+
???+ Abstract "题目大意"
93+
94+
给你一个长度为 $N(2 \le N \le 2 \times 10 ^ 5)$ 的序列 $A = (A_1, A_2, ..., A_N)$。
95+
96+
对于每个 $i = 1, 2, ..., N$,回答以下问题:
97+
98+
- 是否存在 $1 \le j < i$ 满足 $A_j = A_i$?如果存在这样的 $j$,输出最大的 $j$。如果不存在这样的 $j$,输出 `-1`
99+
100+
??? Note "解题思路"
101+
102+
用哈希表存一下每个数字上一次出现的坐标即可。
103+
104+
??? Success "参考代码"
105+
106+
=== "C++"
107+
108+
```c++
109+
#include <iostream>
110+
#include <unordered_map>
111+
112+
using namespace std;
113+
114+
int main()
115+
{
116+
int n;
117+
cin >> n;
118+
unordered_map<int, int> last;
119+
for (int i = 1; i <= n; i++)
120+
{
121+
int x;
122+
cin >> x;
123+
auto it = last.find(x);
124+
if(it == last.end())
125+
{
126+
cout << -1 << ' ';
127+
last[x] = i;
128+
}
129+
else
130+
{
131+
cout << it->second << ' ';
132+
it->second = i;
133+
}
134+
}
135+
return 0;
136+
}
137+
```
138+
139+
---
140+
141+
## [D - Count Simple Paths](https://atcoder.jp/contests/abc378/tasks/abc378_d)
142+
143+
???+ Abstract "题目大意"
144+
145+
给你一个 $H \times W(1 \le H, W \le 10)$ 的网格,每个格子要么是 `#` 表示有障碍物,要么是 `.` 表示没有障碍物。
146+
147+
问:有多少条满足以下条件的路径?
148+
149+
- 从任意一个没有障碍物的格子出发。
150+
- 每一步可以移动到上下左右的相邻格子中。
151+
- 不能进入有障碍物的格子。不能进入到已经走过的格子。
152+
- 路径长度为 $K$(移动恰好 $K$ 次)。
153+
154+
??? Note "解题思路"
155+
156+
dfs 模板题。
157+
158+
??? Success "参考代码"
159+
160+
=== "C++"
161+
162+
```c++
163+
#include <iostream>
164+
#include <string>
165+
#include <vector>
166+
167+
using namespace std;
168+
169+
const int dr[] = {-1, 1, 0, 0};
170+
const int dc[] = {0, 0, -1, 1};
171+
172+
int main()
173+
{
174+
int n, m, k;
175+
cin >> n >> m >> k;
176+
vector<string> g(n);
177+
for (int i = 0; i < n; i++)
178+
cin >> g[i];
179+
int ans = 0;
180+
181+
auto in_graph = [n, m](int r, int c) -> bool
182+
{ return r >= 0 && r < n && c >= 0 && c < m; };
183+
184+
auto dfs = [&in_graph, &g, &ans, k](auto &self, int r, int c, int dep) -> void
185+
{
186+
if (dep == k)
187+
{
188+
ans++;
189+
return;
190+
}
191+
g[r][c] = '#';
192+
for (int i = 0; i < 4; i++)
193+
{
194+
int nr = r + dr[i];
195+
int nc = c + dc[i];
196+
if (in_graph(nr, nc) && g[nr][nc] == '.')
197+
self(self, nr, nc, dep + 1);
198+
}
199+
g[r][c] = '.';
200+
};
201+
202+
for (int r = 0; r < n; r++)
203+
for (int c = 0; c < m; c++)
204+
if (g[r][c] == '.')
205+
dfs(dfs, r, c, 0);
206+
cout << ans << endl;
207+
return 0;
208+
}
209+
```
210+
211+
---
212+
213+
## [E - Mod Sigma Problem](https://atcoder.jp/contests/abc378/tasks/abc378_e)
214+
215+
???+ Abstract "题目大意"
216+
217+
给你一个长度为 $N(1 \le N \le 2 \times 10 ^ 5)$ 的序列 $A = (A_1, A_2, ..., A_N)$ 和一个整数 $M(1 \le M \le 2 \times 10 ^ 5)$。求出 $\sum\limits_{1 \le l \le r \le N}((\sum\limits_{l \le i \le r}A_i) \bmod M)$
218+
219+
??? Note "解题思路"
220+
221+
预处理出前缀和,等价于求出 $\sum\limits_{1 \le l \le r \le N}((s[r]-s[l-1]) \bmod M)$,因为要模 $M$,首先将每个前缀和模 $M$ ,这样可以保证 $0 \le s[i] < M$,于是我们可以将模 $M$ 减法进行拆分:
222+
$$
223+
s[r]-s[l-1] = \left\{
224+
\begin{matrix}
225+
s[r] - s[l-1], & \text{if} \ s[l-1] \le s[r] \\
226+
s[r] - s[l-1] + M, & \text{if} \ s[l-1] > s[r]
227+
\end{matrix}
228+
\right.
229+
$$
230+
这样,在固定 $r$ 的时候,只需要求出前面有多少个 $s[l-1]$ 比 $s[r]$ 小(树状数组维护每个数字出现的次数即可),同时预处理出前缀和的前缀和,再把剩下的 $M$ 补上就好。
231+
232+
??? Success "参考代码"
233+
234+
=== "C++"
235+
236+
```c++
237+
#include <iostream>
238+
#include <vector>
239+
240+
using namespace std;
241+
using LL = long long;
242+
243+
struct FenwickTree
244+
{
245+
int n;
246+
vector<int> t;
247+
248+
FenwickTree(int n) : n(n + 1), t(n + 2) {}
249+
250+
int lowbit(int x) const { return x & -x; }
251+
252+
void add(int x, int k)
253+
{
254+
x++;
255+
for (; x <= n; x += lowbit(x))
256+
t[x] += k;
257+
}
258+
259+
int prefix(int x) const
260+
{
261+
x++;
262+
int sum = 0;
263+
for (; x; x -= lowbit(x))
264+
sum += t[x];
265+
return sum;
266+
}
267+
268+
int query(int l, int r) const { return prefix(r) - prefix(l - 1); }
269+
};
270+
271+
int main()
272+
{
273+
int n, m;
274+
cin >> n >> m;
275+
FenwickTree t(m);
276+
LL ans = 0, sum = 0;
277+
LL sr = 0;
278+
for (int i = 1; i <= n; i++)
279+
{
280+
int a;
281+
cin >> a;
282+
sr = (sr + a) % m;
283+
LL cnt = t.query(sr + 1, m);
284+
ans += i * sr - sum + cnt * m;
285+
sum += sr;
286+
t.add(sr, 1);
287+
}
288+
cout << ans << endl;
289+
return 0;
290+
}
291+
```
292+
293+
---
294+
295+
## [F - Add One Edge 2](https://atcoder.jp/contests/abc378/tasks/abc378_f)
296+
297+
???+ Abstract "题目大意"
298+
299+
给你一棵 $N(3 \le N \le 2 \times 10 ^ 5)$ 个点的树,你可以在任意两点间添加恰好一条边,这会导致图中存在一个环。问:有多少种加边的方式可以让恰好添加一条边的图满足以下条件?
300+
301+
- 加边后的图仍然是简单图。
302+
- 图中环上所有的点的度数都是 $3$
303+
304+
??? Note "解题思路"
305+
306+
加上去的边一定是环上的边,而加边会让两个点的度数都 $+1$,所以一定是在两个度数为 $2$ 的两个点之间加边。
307+
308+
先考虑简单的情况,设有一个点 $w$,且 $w$ 有孩子 $u$ 和 $v$,如果 $d[u] = 2$ 且 $d[v] = 2$ 且 $d[w] = 3$,那么就可以连上 $u$ 和 $v$ 的边满足条件。进一步可以发现,如果 $w$ 有 $2$ 个度数为 $2$ 的孩子,就有 $1$ 种连线方案,如果有 $3$ 个度数为 $2$ 的孩子,就有 $3$ 种连线方案。
309+
310+
但这种做法还不够完善。一般的,设 $u$ 和 $v$ 的 lca 是 $w$,如果 $w$ 到 $u$ 以及 $w$ 到 $v$ 这两条路径的节点度数都是 $3$,且 $d[u] = 2$ 且 $d[v] = 2$,连上 $u$ 和 $v$ 的边也是满足条件的。为了处理这种情况,只需要把所有度数为 $3$ 且相邻的点合并成 $1$ 个,然后枚举这些度数为 $3$ 的节点有多少个度数为 $2$ 的孩子就可以。
311+
312+
??? Success "参考代码"
313+
314+
=== "C++"
315+
316+
```c++
317+
#include <iostream>
318+
#include <vector>
319+
320+
using namespace std;
321+
using LL = long long;
322+
323+
int main()
324+
{
325+
int n;
326+
cin >> n;
327+
vector<vector<int>> edge(n + 1);
328+
vector<int> deg(n + 1);
329+
for (int i = 0; i < n - 1; i++)
330+
{
331+
int u, v;
332+
cin >> u >> v;
333+
edge[u].push_back(v);
334+
edge[v].push_back(u);
335+
deg[u]++;
336+
deg[v]++;
337+
}
338+
vector<int> p(n + 1);
339+
for(int u = 1; u <= n; u++)
340+
p[u] = u;
341+
342+
auto dfs_for_merge = [&edge, &p, &deg](auto &self, int u, int fa) -> void
343+
{
344+
for(auto v : edge[u])
345+
{
346+
if(v == fa)
347+
continue;
348+
if(deg[u] == 3 && deg[v] == 3)
349+
p[v] = p[u];
350+
self(self, v, u);
351+
}
352+
};
353+
354+
dfs_for_merge(dfs_for_merge, 1, 0);
355+
vector<int> two_deg_child_cnt(n + 1);
356+
for(int u = 1; u <= n; u++)
357+
{
358+
int pu = p[u];
359+
if(pu != u)
360+
{
361+
for(auto v : edge[u])
362+
if(deg[v] == 2)
363+
two_deg_child_cnt[pu]++;
364+
}
365+
else if(deg[u] == 3)
366+
{
367+
for(auto v : edge[u])
368+
if(deg[v] == 2)
369+
two_deg_child_cnt[u]++;
370+
}
371+
}
372+
LL ans = 0;
373+
for(int u = 1; u <= n; u++)
374+
{
375+
LL c = two_deg_child_cnt[u];
376+
ans += c * (c - 1) / 2;
377+
}
378+
cout << ans << endl;
379+
return 0;
380+
}
381+
```
382+
383+
---
384+

0 commit comments

Comments
 (0)