2024-10-29 14:46阅读: 348评论: 0推荐: 1

AtCoder Beginner Contest 377

上周六咕咕咕了

省流版
  • A. 排序判断即可
  • B. 枚举判断即可
  • C. 记录覆盖位置去重,总数-覆盖数即可
  • D. 枚举右端点,考虑符合条件的左端点数量即可
  • E. 考虑排列的ipi图,考虑操作数与走的边数关系,利用环循环节算偏移量即可
  • F. 考虑每个皇后实际覆盖的位置,枚举先前皇后计算覆盖交集去重,累加实际覆盖数即可
  • G. 将操作转换成Tries树的节点移动,即一条链上的到达最近叶子的距离,取最小值即可

A - Rearranging ABC (abc377 A)

题目大意

给定三个字母,问能否组成ABC

解题思路

排个序看是否是ABC即可。

神奇的代码
#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);
string s;
cin >> s;
sort(s.begin(), s.end());
if (s == "ABC")
cout << "Yes" << '\n';
else
cout << "No" << '\n';
return 0;
}


B - Avoid Rook Attack (abc377 B)

题目大意

国际象棋,车,上下左右任意走,

8×8的棋盘,给定 m个车的位置。

问有多少位置,不会被车的范围覆盖。

解题思路

一个车就覆盖一行和一列。

因此就set记录被覆盖了的行和列,最后还有x行和 y列没覆盖,答案就是 xy

当然直接花 O(84)枚举位置+判断是否被覆盖也可以。

神奇的代码
#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);
set<int> r, c;
for (int i = 0; i < 8; ++i) {
string s;
cin >> s;
for (int j = 0; j < 8; ++j)
if (s[j] == '#') {
r.insert(i);
c.insert(j);
}
}
int ans = (8 - r.size()) * (8 - c.size());
cout << ans << '\n';
return 0;
}


C - Avoid Knight Attack (abc377 C)

题目大意

国际象棋,马,八个方向的字走法。

n×n的棋盘,给定 m个马的位置。

问有多少位置,不会被马的范围覆盖。

解题思路

因为一个🐎只覆盖八个位置,用set记录每个每个🐎覆盖的八个位置并去重,假设是去重后有x位置被🐎覆盖。

答案就是 n2x

神奇的代码
#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, m;
cin >> n >> m;
set<array<int, 2>> forbid;
array<int, 8> dx = {2, 1, -1, -2, -2, -1, 1, 2};
array<int, 8> dy = {1, 2, 2, 1, -1, -2, -2, -1};
for (int i = 0; i < m; i++) {
int x, y;
cin >> x >> y;
forbid.insert({x, y});
for (int j = 0; j < 8; j++) {
int nx = x + dx[j];
int ny = y + dy[j];
if (nx >= 1 && nx <= n && ny >= 1 && ny <= n) {
forbid.insert({nx, ny});
}
}
}
LL ans = 1ll * n * n - forbid.size();
cout << ans << '\n';
return 0;
}


D - Many Segments 2 (abc377 D)

题目大意

一维数轴,给定若干条线段[li,ri]

[l,r]数量,其不完全包含上述中的任意线段。

解题思路

枚举r,考虑有多少个 l符合要求。

[l,r]不完全包含任何线段,因此只需考虑 rir的线段,然后 l>li即可。

l的取值范围即为(\max_{r_i \leq r} l_i, r],个数即为rmaxrirli,对所有的r求和即为答案。

对线段的右端点从小到大排序,由于是从小到大枚举的r,因此maxrirli可以在枚举的过程 O(1)维护。

神奇的代码
#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, m;
cin >> n >> m;
vector<array<int, 2>> seg(n);
for (auto& s : seg) {
cin >> s[0] >> s[1];
}
sort(seg.begin(), seg.end(),
[](const array<int, 2>& a, const array<int, 2>& b) {
return a[1] < b[1];
});
LL ans = 0;
int maxl = 0;
int cur = 0;
for (int i = 1; i <= m; ++i) {
while (cur < n && seg[cur][1] <= i) {
maxl = max(maxl, seg[cur][0]);
++cur;
}
ans += i - maxl;
}
cout << ans << '\n';
return 0;
}


E - Permute K times 2 (abc377 E)

题目大意

给定一个排列pi,进行k次操作。

每次操作,同时将所有的 pi替换为 ppi

问进行k次操作后的排列。

解题思路

排列的变换,可以考虑建图ipi,即有若干个环。

通过手试会发现,进行第一次操作,相当于走1条边,进行第二次操作,相当于走2条边, 第三次走4条...因为本次走的点,其之前也走过了同样的步数。

即进行 k次,将会走 i=0k12i=2k1条边,由于一个环的循环节大小为环的点数,因此其对环大小取后即为进行了 k次操作后的位置a,其数就是 pa

因此找出每个环,假设环大小sz,用快速幂算出2k1%sz ,即进行 k次操作后的偏移量,然后求该环上所有点最终的偏移位置即可。

神奇的代码
#include <bits/stdc++.h>
using namespace std;
using LL = long long;
long long qpower(long long a, long long b, long long mo) {
long long qwq = 1;
while (b) {
if (b & 1)
qwq = qwq * a % mo;
a = a * a % mo;
b >>= 1;
}
return qwq;
}
int main(void) {
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
int n;
LL k;
cin >> n >> k;
vector<int> p(n);
for (auto& i : p) {
cin >> i;
--i;
}
vector<int> vis(n);
vector<int> ans(n);
for (int i = 0; i < n; i++) {
if (vis[i])
continue;
int j = i;
vector<int> cycle;
while (!vis[j]) {
vis[j] = 1;
cycle.push_back(j);
j = p[j];
}
int len = cycle.size();
for (int i = 0; i < len; i++) {
int step = (qpower(2, k, len) + len - 1) % len;
int j = (i + step) % len;
ans[cycle[i]] = cycle[j];
}
}
for (auto i : ans)
cout << p[i] + 1 << ' ';
return 0;
}


F - Avoid Queen Attack (abc377 F)

题目大意

国际象棋,皇后,横竖两个对角线的任意走。

n×n的棋盘,给定 m个皇后的位置。

问有多少位置,不会被皇后的范围覆盖。

解题思路

同样考虑所有皇后覆盖了多少位置,然后用总数减去覆盖的位置。

但与🐎不同的是,一个皇后覆盖的数量位置和O(n)同级,不能像🐎一样记录所有覆盖位置。

考虑每个皇后实际覆盖的位置数,该位置数是去除了先前考虑的皇后的覆盖位置。

首先,可以算出该皇后可以覆盖的数量,即横竖两个对角线的和。然后考虑去除已经算过的覆盖的位置。

由于m1000,因此可以直接枚举先前考虑的皇后,然后计算皇后重叠的覆盖部分,就能得到该皇后实际覆盖的位置。

所有的皇后的实际覆盖数的和,用总数减去即为答案。时间复杂度为O(m2)

想法比较朴素,实现需要一些细节。

考虑皇后的重叠部份的计算,由于覆盖的部分还会重复覆盖(即皇后ab重叠的部分,与皇后 ac 也有重叠),因此计算重复覆盖时还得去重。

如果同行同列或者同对角线, 由于其数量级是O(n),不能全部记录被覆盖的地方,可以用四个变量表示该行列或对角线是否被覆盖。

然后考虑

  • 皇后的与另外的俩对角线的交集,最多三个点。
  • 皇后的与另外的俩对角线的交集,最多三个点。
  • 皇后的俩对角线与另外的一对角线的交集,最多三个点。

遍历先前考虑的所有皇后,得到这些覆盖的点,去重后即为先前已经考虑过的覆盖的点。用该皇后可以覆盖的位置数减去考虑过的,即为该皇后实际覆盖的点数。

至于计算交集点,两条斜对角线的计算方式需要解一个二元一次方程组。

代码实现里,除row外,其余的下标都是y轴的。注意中心点被重复考虑的情况。

神奇的代码
#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, m;
cin >> n >> m;
vector<array<int, 2>> a(m);
for (auto& x : a)
cin >> x[0] >> x[1];
LL tot = 0;
auto calc_diag = [&](int x, int y) {
return min(x, n - y + 1) + min(n - x + 1, y) - 1;
};
auto calc_diag2 = [&](int x, int y) {
return min(x, y) + min(n - x + 1, n - y + 1) - 1;
};
auto intersect = [&](int x1, int y1, int x2, int y2, int& tmp) {
int d1 = x1 + y1, d2 = x2 - y2;
int x = (d1 + d2), y = (d1 - d2);
if ((x & 1) || (y & 1))
return false;
x = x / 2;
y = y / 2;
tmp = y;
return x >= 1 && x <= n && y >= 1 && y <= n;
};
auto intersect2 = [&](int x1, int y1, int x2, int y2, int& tmp) {
int d1 = x1 - y1, d2 = x2 + y2;
int x = (d1 + d2), y = (d2 - d1);
if ((x & 1) || (y & 1))
return false;
x = x / 2;
y = y / 2;
tmp = y;
return x >= 1 && x <= n && y >= 1 && y <= n;
};
auto calc = [&](int x, int y) {
LL cnt = 0;
cnt += n;
cnt += n;
cnt += calc_diag(x, y);
cnt += calc_diag2(x, y);
cnt -= 3;
return cnt;
};
auto in_range = [&](int x) { return x >= 1 && x <= n; };
for (int i = 0; i < m; i++) {
auto [x, y] = a[i];
LL forbid = calc(x, y);
bool row = false, col = false, diag = false, diag2 = false;
set<int> forbid_row, forbid_col, forbid_diag, forbid_diag2;
for (int j = 0; j < i; ++j) {
auto [x2, y2] = a[j];
if (x == x2)
row = true;
if (y == y2)
col = true;
if (x + y == x2 + y2)
diag = true;
if (x - y == x2 - y2)
diag2 = true;
forbid_row.insert(y2);
if (in_range(x2 + y2 - x))
forbid_row.insert(x2 + y2 - x);
if (in_range(y2 - x2 + x))
forbid_row.insert(y2 - x2 + x);
forbid_col.insert(x2);
if (in_range(x2 + y2 - y))
forbid_col.insert(x2 + y2 - y);
if (in_range(x2 - y2 + y))
forbid_col.insert(x2 - y2 + y);
int tmp;
if (in_range(x + y - x2))
forbid_diag.insert(x + y - x2);
if (in_range(x + y - y2))
forbid_diag.insert(y2);
if (intersect(x, y, x2, y2, tmp))
forbid_diag.insert(tmp);
if (in_range(y - x + x2))
forbid_diag2.insert(y - x + x2);
if (in_range(x - y + y2))
forbid_diag2.insert(y2);
if (intersect2(x, y, x2, y2, tmp))
forbid_diag2.insert(tmp);
}
int center = 0;
if (row) {
forbid -= n;
center++;
} else {
forbid -= forbid_row.size();
center += forbid_row.count(y);
}
if (col) {
forbid -= n;
center++;
} else {
forbid -= forbid_col.size();
center += forbid_col.count(x);
}
if (diag) {
forbid -= calc_diag(x, y);
center++;
} else {
forbid -= forbid_diag.size();
center += forbid_diag.count(y);
}
if (diag2) {
forbid -= calc_diag2(x, y);
center++;
} else {
forbid -= forbid_diag2.size();
center += forbid_diag2.count(y);
}
tot += forbid;
tot += max(center - 1, 0);
}
LL ans = 1ll * n * n - tot;
cout << ans << '\n';
return 0;
}


G - Edit to Match (abc377 G)

题目大意

给定n个字符串,对于每个字符串 si,回答以下问题。

进行最少次数操作,使得 si为空,或者存在j<i,使得si=sj

操作分两种:

  • 删去si末尾的字符。
  • si末尾添加任意字符。

解题思路

对尾部操作,对这些字符串建立一颗Trie树,操作一相当于节点往父亲走,操作二相当于往儿子方向走。然后以最小的步数到达叶子或者根。

考虑预处理数组mindeepi,表示从点 i出发,到达叶子的最小步数(其实就是子树的最小叶子深度-当前点深度),然后对该字符串所对应的 Tries树的所有节点的mindeepi+删去末尾字符到达该点的操作数取个最小值即可。

插入字符串时需要更新 mindeepi,需要更新的也刚好就是插入时经过的所有节点。

神奇的代码
#include <bits/stdc++.h>
using namespace std;
using LL = long long;
const int inf = 1e9 + 7;
const int SZ = 26;
template <typename T, typename K> struct Trie {
struct node {
bool is_terminal = false;
int min_deep = inf;
array<int, SZ> children{};
};
int cast(K val) {
int ret = val - 'a';
assert(ret < SZ and ret >= 0);
return ret;
}
vector<node> tree;
Trie(K val) { tree.push_back(node()); }
int insert(const T& sequence) {
int cur = 0;
int ans = sequence.size();
int cost = sequence.size();
for (int i = 0; i < (int)sequence.size(); i++) {
K value = sequence[i];
if (tree[cur].children[cast(value)] == 0) {
tree[cur].children[cast(value)] = (int)tree.size();
tree.push_back(node());
}
cur = tree[cur].children[cast(value)];
--cost;
ans = min(ans, cost + tree[cur].min_deep);
tree[cur].min_deep = min(tree[cur].min_deep, cost);
}
tree[cur].is_terminal = true;
return ans;
}
};
int main(void) {
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
int n;
cin >> n;
Trie<string, char> trie('a');
for (int i = 0; i < n; i++) {
string s;
cin >> s;
int ans = trie.insert(s);
cout << ans << '\n';
}
return 0;
}


本文作者:~Lanly~

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

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

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