AtCoder Beginner Contest 378
省流版
- A. 判断奇偶性即可
- B. 根据余数计算偏移天数即可
- C. 用
map
记录每个数出现的位置即可 - D. 枚举起点,枚举每步的方向,朴素搜索即可
- E. 考虑前缀和的两数相减代替区间和的情况,减为负数则加回正数,用树状数组维护减为负数的情况数
- F. 枚举点,作为连边的俩个点的
lca
,考虑维护路径点度数为的数量,组合即可
A - Pairing (abc378 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 = 4; array<int, 4> cnt{}; while (n--) { int a; cin >> a; cnt[a - 1]++; } int ans = 0; for (auto& i : cnt) { ans += i / 2; } cout << ans << '\n'; return 0; }
B - Garbage Collection (abc378 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<array<int, 2>> a(n); for (auto& x : a) cin >> x[0] >> x[1]; int Q; cin >> Q; while (Q--) { int t, d; cin >> t >> d; --t; auto [q, r] = a[t]; int ans = (r - d % q + q) % q; cout << d + ans << '\n'; } return 0; }
C - Repeating (abc378 C)
题目大意
给定一个数组,构造相同长度的数组 ,满足 是 上一次出现的位置,或者 。
解题思路
直接用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; map<int, int> pos; for (int i = 0; i < n; i++) { int x; cin >> x; int ans = pos.count(x) ? pos[x] + 1 : -1; cout << ans << " \n"[i == n - 1]; pos[x] = i; } return 0; }
D - Count Simple Paths (abc378 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 h, w, k; cin >> h >> w >> k; vector<string> s(h); for (auto& x : s) cin >> x; int ans = 0; array<int, 4> dx = {0, 1, 0, -1}; array<int, 4> dy = {1, 0, -1, 0}; auto ok = [&](int x, int y) -> bool { return 0 <= x && x < h && 0 <= y && y < w && s[x][y] != '#'; }; vector<vector<int>> visit(h, vector<int>(w, 0)); auto dfs = [&](auto dfs, int x, int y, int cnt) -> void { if (cnt == k) { ++ans; return; } visit[x][y] = 1; for (int i = 0; i < 4; ++i) { int nx = x + dx[i]; int ny = y + dy[i]; if (ok(nx, ny) && !visit[nx][ny]) { dfs(dfs, nx, ny, cnt + 1); } } visit[x][y] = 0; }; for (int i = 0; i < h; ++i) { for (int j = 0; j < w; ++j) { if (s[i][j] == '#') continue; dfs(dfs, i, j, 0); } } cout << ans << '\n'; return 0; }
E - Mod Sigma Problem (abc378 E)
题目大意
给定数组,和模数 。求
解题思路
预处理前缀和,则区间和 可表示为 。
我们枚举,然后求所有的 ,其区间和的和时多少。
由于取模的缘故,其结果但可能为负数,此时要,但有多少个需要加呢?自然就是的那些 。
由于 只有,可以开一个计数的桶 表示数字 出现的次数,那么上述的 的数量就是 。假设其数量为,那么当前 对答案的贡献即为 。中间一项就是前缀和的前缀,而 就是。
关于的求法,涉及到区间求和和单点修改,因此可以用权值树状数组或权值线段树维护这个桶即可。
神奇的代码
#include <bits/stdc++.h> using namespace std; using LL = long long; // starting from 0 template <typename T> class fenwick { public: vector<T> fenw; int n; fenwick(int _n) : n(_n) { fenw.resize(n); } void modify(int x, T v) { while (x < n) { fenw[x] += v; x |= (x + 1); } } T get(int x) { T v{}; while (x >= 0) { v += fenw[x]; x = (x & (x + 1)) - 1; } return v; } }; int main(void) { ios::sync_with_stdio(false); cin.tie(0); cout.tie(0); int n, m; cin >> n >> m; int presum = 0; LL ppresum = 0; fenwick<int> cnt(m); LL ans = 0; cnt.modify(0, 1); for (int i = 0; i < n; ++i) { int a; cin >> a; a %= m; presum = (presum + a) % m; int cc = i + 1 - cnt.get(presum); ans += 1ll * (i + 1) * presum - ppresum + 1ll * m * cc; cnt.modify(presum, 1); ppresum += presum; } cout << ans << '\n'; return 0; }
下述想的比较复杂度,同样是枚举,然后看所有区间和的变化。分两类,一类是直接 ,另一类是 。
因为区间和的范围同样在,所以用权值线段树维护 表示区间和的数量,当新增 时,线段树里的数据都是的区间和个数,考虑计算贡献,即属于第一类, 属于第二类。
分别计算贡献后,考虑怎么变化,即怎么变成的区间和个数。由于所有数增加了,因此会进行一个整体偏移 ,即,但直接这么做是 的,不能这么做。但考虑到是整体偏移,我们可以记录此时表示 的位置,即原来在时, 表示区间和为的个数,在增加 后, 就表示区间和为 的个数。即我们自定义的位置,这样就是整体偏移了。
神奇的代码
#include <bits/stdc++.h> using namespace std; using LL = long long; const int N = 2e5 + 8; class segment { #define lson (root << 1) #define rson (root << 1 | 1) public: LL cnt[N << 2]; LL sum[N << 2]; LL lazy[N << 2]; int n; void pushup(int root) { cnt[root] = cnt[lson] + cnt[rson]; sum[root] = sum[lson] + sum[rson]; } void build(int root, int l, int r) { if (l == r) { cnt[root] = 0; sum[root] = 0; return; } int mid = (l + r) >> 1; build(lson, l, mid); build(rson, mid + 1, r); pushup(root); } void pushdown(int root, int l, int mid, int r) { if (lazy[root]) { sum[lson] += lazy[root] * cnt[lson]; sum[rson] += lazy[root] * cnt[rson]; lazy[lson] += lazy[root]; lazy[rson] += lazy[root]; lazy[root] = 0; } } void update(int root, int l, int r, int L, int R, LL val) { if (L > R) return; if (L <= l && r <= R) { sum[root] += val * cnt[root]; lazy[root] += val; return; } int mid = (l + r) >> 1; pushdown(root, l, mid, r); if (L <= mid) update(lson, l, mid, L, R, val); if (R > mid) update(rson, mid + 1, r, L, R, val); pushup(root); } void insert(int root, int l, int r, int pos, LL val) { if (l == r) { cnt[root] += 1; sum[root] += val; return; } int mid = (l + r) >> 1; pushdown(root, l, mid, r); if (pos <= mid) insert(lson, l, mid, pos, val); else insert(rson, mid + 1, r, pos, val); pushup(root); } pair<int, LL> query(int root, int l, int r, int L, int R) { if (L <= l && r <= R) { return {cnt[root], sum[root]}; } int mid = (l + r) >> 1; pushdown(root, l, mid, r); pair<int, LL> ans = {0, 0}; if (L <= mid) { auto tmp = query(lson, l, mid, L, R); ans.first += tmp.first; ans.second += tmp.second; } if (R > mid) { auto tmp = query(rson, mid + 1, r, L, R); ans.first += tmp.first; ans.second += tmp.second; } return ans; } pair<int, LL> query_from(int root, int l, int r, int L, int R) { if (L > R) return {0, 0}; L = (L % n + n) % n + 1; R = (R % n + n) % n + 1; debug(L, R); if (L <= R) return query(root, l, r, L, R); pair<int, LL> ans = {0, 0}; auto tmp = query(root, l, r, L, r); ans.first += tmp.first; ans.second += tmp.second; tmp = query(root, l, r, 1, R); ans.first += tmp.first; ans.second += tmp.second; return ans; } void update_from(int root, int l, int r, int L, int R, LL val) { if (L > R) return; L = (L % n + n) % n + 1; R = (R % n + n) % n + 1; if (L <= R) update(root, l, r, L, R, val); else { update(root, l, r, L, r, val); update(root, l, r, 1, R, val); } } } sg; int main(void) { ios::sync_with_stdio(false); cin.tie(0); cout.tie(0); int n, m; cin >> n >> m; vector<int> a(n); for (auto& x : a) { cin >> x; x %= m; } sg.build(1, 1, m); sg.n = m; int l = 0; LL ans = 0; for (int i = 0; i < n; i++) { int r = l + m - a[i]; auto [cnt, sum] = sg.query_from(1, 1, m, l, r - 1); ans += 1ll * cnt * a[i] + sum; auto [cnt2, sum2] = sg.query_from(1, 1, m, r, l + m - 1); ans += 1ll * cnt2 * (a[i] - m) + sum2; sg.update_from(1, 1, m, l, r - 1, a[i]); sg.update_from(1, 1, m, r, l + m - 1, a[i] - m); l = r % m; sg.insert(1, 1, m, (l + a[i]) % m + 1, a[i]); ans += a[i]; } cout << ans << '\n'; return 0; }
F - Add One Edge 2 (abc378 F)
题目大意
给定一棵树,求加一条边的方案数,使得没有重边,且环上的所有点的度数为。
解题思路
加一条边,首先这两个点的度数为 ,然后假设 路径上的所有点的度数为 。
假设 的最近公共祖先是 ,即 , 的所有点的度数为 。
注意到这是一个向父亲方向的,要求路径上所有点为 的信息,可以通过预处理 表示从 往父亲走,其点度为 的最浅深度之类的信息。然后我们只需枚举 ,看 与 的深度关系,即可知道加的这条边 是否符合要求。
但上述时间复杂度为 ,我们考虑枚举 ,然后看其子树有多少对符合条件的 。
从 的角度,我们需要什么信息?即从该 往儿子方向走,其一路点度数为 ,最后一个点度数为 ,这样的路径条数。不同子树之间的这类点就可以连边(当然 的度数也要是 )。
注意重边的情况,即 度数为 ,其一个儿子的度数也为 。
上述过程可能就是树形(?表示 子树内,一路往儿子方向,其点度数为 ,最后一个点度数为 的路径条数,然后合并不同子树时计算匹配的点对。
神奇的代码
#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); vector<int> du(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); du[u]++; du[v]++; } LL ans = 0; auto dfs = [&](auto dfs, int u, int fa) -> int { int ret = 0; for (int v : edge[u]) { if (v == fa) continue; int nxt = dfs(dfs, v, u); if (du[u] == 2) ans += nxt; else if (du[u] == 3) ans += 1ll * nxt * ret; ret += nxt; } if (du[u] == 2) return 1; else if (du[u] == 3) return ret; else return 0; }; dfs(dfs, 0, 0); int extra = 0; for (int u = 0; u < n; u++) { for (auto v : edge[u]) { if (du[u] == 2 && du[v] == 2) extra++; } } ans -= extra / 2; cout << ans << '\n'; return 0; }
G - Everlasting LIDS (abc378 G)
题目大意
给定,求 的全排列数量,满足以下条件:
- 最长上升子序列长度为
- 最长下降子序列长度为
- 存在 使得在末尾增加一个数 ,其上述两个长度不改变。
输出数量对 取模。
解题思路
<++>
神奇的代码
本文作者:~Lanly~
本文链接:https://www.cnblogs.com/Lanly/p/18522694
版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步