2024-11-02 23:31阅读: 460评论: 0推荐: 1

AtCoder Beginner Contest 378

省流版
  • A. 判断奇偶性即可
  • B. 根据余数计算偏移天数即可
  • C. 用map记录每个数出现的位置即可
  • D. 枚举起点,枚举每步的方向,朴素搜索即可
  • E. 考虑前缀和的两数相减代替区间和的情况,减为负数则加回正数,用树状数组维护减为负数的情况数
  • F. 枚举点,作为连边的俩个点的lca,考虑维护路径点度数为33..32的数量,组合即可

A - Pairing (abc378 A)

题目大意

给定4个数。

问做的操作数,每次选两个相同的数,然后丢弃。

解题思路

统计每个数的出现次数cnti,答案就是 cnti2

神奇的代码
#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)

题目大意

n种垃圾,第 i种垃圾会在天数 d收取,其中 d满足 d%pi=ri

回答 q个询问,每个询问问在第 di天丢的第ti种垃圾,会在第几天被收取。如果当天丢且当天可收取,则会被收取。

解题思路

假设j=ti,先算r=di%pj,如果 rrj,那么很显然多过rjr天就会被收取。否则要过一个循环,即pjr+rj天才会被收取。

神奇的代码
#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)

题目大意

给定一个数组a,构造相同长度的数组 b,满足 biai上一次出现的位置,或者 1

解题思路

直接用map记录每个元素ai上次出现的位置,然后输出map[ai]即可

神奇的代码
#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)

题目大意

给定一张二维平面,有障碍物。

问方案数,从任意点出发,上下左右走,可以走k步,不经过障碍物,且每个点只访问一次。

解题思路

由于平面10×10k11,直接花O(hw)枚举点,然后花(4k)遍历所有方案。 其时间复杂度为O(hw4k),约为 1e8,可过。

神奇的代码
#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)

题目大意

给定数组a,和模数 m。求 1lrn((lirai)%m)

解题思路

预处理前缀和sum[i]=(jiai)%m,则区间和 [l,r]可表示为 sum[r]sum[l1]

我们枚举r,然后求所有的 lr,其区间和的和时多少。

由于取模的缘故,其结果但可能为负数,此时要+m,但有多少个l需要加呢?自然就是sum[l1]>sum[r]的那些 l

由于 sum[i]m只有1e5,可以开一个计数的桶 tree[i]表示数字 i出现的次数,那么上述的 l的数量就是 i>sum[r]tree[i]。假设其数量为k,那么当前 r对答案的贡献即为 (lrsum[r]sum[l1])+km=r×sum[r]lrsum[l1]+km。中间一项就是前缀和的前缀,而 k就是i>sum[r]tree[i]

关于k的求法,涉及到区间求和和单点修改,因此可以用权值树状数组或权值线段树维护这个桶即可。

神奇的代码
#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;
}

下述想的比较复杂度,同样是枚举r,然后看所有[l,r1][l,r]区间和的变化。分两类,一类是直接[l,r1]+ar=[l,r] ,另一类是[l,r1]+arm=[l,r]

因为区间和的范围同样在[0,m1],所以用权值线段树维护 cnti表示区间和[l,r]=i的数量,当新增 ar时,线段树里的数据都是[l..r1]的区间和个数,考虑计算贡献,即cnt0..mai属于第一类, cntmai..m1属于第二类。

分别计算贡献后,考虑cnti怎么变化,即怎么变成[l..r]的区间和个数。由于所有数增加了ar,因此cnti会进行一个整体偏移 ,即cnti+ar=cnti,但直接这么做是 O(n)的,不能这么做。但考虑到是整体偏移,我们可以记录此时表示 cnt0的位置,即原来在[l,r1]时,cnt0 表示区间和为0的个数,在增加 ar后, cntmar就表示区间和为 0的个数。即我们自定义cnt0的位置,这样就是整体偏移了。

神奇的代码
#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)

题目大意

给定一棵树,求加一条边的方案数,使得没有重边,且环上的所有点的度数为3

解题思路

加一条边uv,首先这两个点的度数为 2,然后假设 uv路径上的所有点的度数为 3

假设 u,v的最近公共祖先是 lca,即 ulcavlca的所有点的度数为 3

注意到这是一个向父亲方向的,要求路径上所有点为 3的信息,可以通过预处理 up[i]表示从 i往父亲走,其点度为 3的最浅深度之类的信息。然后我们只需枚举 u,v,看 up[u],up[v]lca的深度关系,即可知道加的这条边 uv是否符合要求。

但上述时间复杂度为 O(n2),我们考虑枚举 lca,然后看其子树有多少对符合条件的 u,v

lca的角度,我们需要什么信息?即从该 lca往儿子方向走,其一路点度数为 3,最后一个点度数为 2,这样的路径条数。不同子树之间的这类点就可以连边(当然 lca的度数也要是 3)。

注意重边的情况,即 lca度数为 2,其一个儿子的度数也为 2

上述过程可能就是树形dp(?dp[i]表示 i子树内,一路往儿子方向,其点度数为 3,最后一个点度数为 2的路径条数,然后合并不同子树时计算匹配的点对。

神奇的代码
#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)

题目大意

给定a,b,m,求 1ab的全排列数量,满足以下条件:

  • 最长上升子序列长度为a
  • 最长下降子序列长度为b
  • 存在 n使得在末尾增加一个数 n+0.5,其上述两个长度不改变。

输出数量对 m取模。

解题思路

<++>

神奇的代码


本文作者:~Lanly~

本文链接:https://www.cnblogs.com/Lanly/p/18522694

版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。

posted @   ~Lanly~  阅读(460)  评论(0编辑  收藏  举报
点击右上角即可分享
微信分享提示
💬
评论
📌
收藏
💗
关注
👍
推荐
🚀
回顶
收起
  1. 1 404 not found REOL
404 not found - REOL
00:00 / 00:00
An audio error has occurred.