官方题解|排位赛#5
2024-03-11 14:31:38
发布于:浙江
直播预告
直播时间:3月9日(周六)下午4点-6点
直播地址: B站ACGO官方账号
直播内容:排位赛#5复盘(欢迎观看大型翻车现场)
主讲人:アイドル老师
ACGO 第五次排位赛题解
T1、 Acgo社区
题目解析
考虑使用一种数据结构来高效维护用户 和用户 之间的关系。
使用 map<int, set<int>> user
来维护每位用户关注的所有其他用户:
user[A]
表示用户 关注的所有用户的集合。
如果满足条件 user[A]
中存在 且 user[B]
中存在 则说明 和 目前属于互相关注的状态。
AC代码
#include <bits/stdc++.h>
using namespace std;
int main() {
cin.tie(nullptr)->sync_with_stdio(false);
int _; cin >> _;
while (_--) {
int n, q;
cin >> n >> q;
map<int, set<int>> fans;
for (int i = 0; i < q; ++i) {
int t, a, b;
cin >> t >> a >> b;
if (t == 1)
user[a].insert(b);
else if (t == 2)
user[a].erase(b);
else
cout << (user[a].count(b) and user[b].count(a) ? "Yes" : "No") << '\n';
}
}
return 0;
}
复杂度分析
对于每个操作时间复杂度为 ,总时间复杂度为 。
T2、添加元素让数组变为好数组
提示
题目解析
任意数字与自己异或的结果为 。
令原数组和为 ,异或和为 ,那么显然我们可以在数组后添加两个元素: 和 。
这样对于添加元素后的数组的和为 ,异或和为 ,满足题目要求。
AC代码
#include <bits/stdc++.h>
using namespace std;
int main() {
cin.tie(nullptr)->sync_with_stdio(false);
int _; cin >> _;
while (_--) {
int n; cin >> n;
auto sum = 0LL, xsum = 0LL;
for (int i = 0; i < n; ++i) {
int x; cin >> x;
sum += x;
xsum ^= x;
}
cout << 2 << '\n';
cout << xsum << ' ' << sum + xsum << '\n';
}
return 0;
}
复杂度分析
遍历一遍数组求得 和 ,时间复杂度 。
T3、替换子串使两串相等
题目解析
两种操作本质上就是可以把 中的 'a'
向右移动,'c'
向左移动。
所以把 和 中的所有字符 'b'
去掉之后,两个字符串应该是相同的。
另外,由于 'a'
不能向左移动,'c'
不能向右移动,所以要检查字符串 和 中,对应的字符 'a'
和字符 'b'
的下标。
对于字符 'a'
,在 中每个字符 'a'
在字符串 中都应该 有一个下标大于等于 它的 'a'
与之对应;
相对对应的,在 中每个字符 'c'
在字符串 中都应该 有一个下标小于等于 它的 'c'
与之对应。
AC代码
#include <bits/stdc++.h>
using namespace std;
int main() {
cin.tie(nullptr)->sync_with_stdio(false);
int _; cin >> _;
while (_--) {
int n; cin >> n;
string s, t;
cin >> s >> t;
string a, b;
for (auto &x : s)
if (x != 'b') a.push_back(x);
for (auto &x : t)
if (x != 'b') b.push_back(x);
if (a != b) {
cout << "NO" << '\n';
continue;
}
auto check = [&]() {
vector<int> idx;
for (int i = 0; i < n; ++i)
if (s[i] != 'b') idx.push_back(i);
for (int i = 0, j = 0; i < n; ++i) {
if (t[i] == 'b') continue;
if (t[i] == 'a' and i < idx[j]) return false;
if (t[i] == 'c' and i > idx[j]) return false;
++j;
}
return true;
};
cout << (check() ? "YES" : "NO") << '\n';
}
return 0;
}
复杂度分析
检查 和 去掉所有字符 'b'
之后是否相等时间复杂度为 。
检查 中字符 'a'
和 'b'
相对于在字符串 中的位置时间复杂度为 。
T4、使两方格相等的最少操作次数
题目解析
方格 的状态可以用 来表示,其中 是代表 行 原始位置的排列,而 是代表 列 原始位置的排列。
一开始 ;交换相邻的行或列相当于交换 或 中相邻的两个元素。
另外,当前方格中 上的整数可以从 的初始状态的 上的整数获得。
例如,对于题目中第一个样例转换的过程,状态 变化为
的总数是 的排列的数量和 的排列的数量的乘积即 。
因此,我们对这 个 ,即 的排列 和 的排列 中的每一个,执行以下操作:
- 首先,根据当前的 和 构造方格 并判断和方格 是否相等。
- 如果相等,求从初始状态到达当前 的最小操作次数。
如果没有任意一对 和 构造出的方格 与方格 相同,那么就不可能使方格 和方格 相等,输出 。
从初始状态到达当前状态 所需的最小操作数是以下操作之和:
- 通过重复交换相邻元素从初始序列 得到排列 所需的最少操作次数;
- 通过重复交换相邻元素从初始序列 得到排列 所需的最小操作次数;
从排列 开始,通过重复交换相邻的两个元素得到所需的排列 所需的运算次数为排列 中的 逆序对 的数量,即
- , 的 的个数。
因此,从初始状态到 所需的操作次数就是 的 逆序对 的数量与 的 逆序对 的数量之和。
AC代码
#include <bits/stdc++.h>
using namespace std;
int count(vector<int> &a) {
int n = a.size(), res = 0;
for (int i = 0; i < n; ++i)
for (int j = i + 1; j < n; ++j)
if (a[i] > a[j]) ++res;
return res;
}
int main() {
cin.tie(nullptr)->sync_with_stdio(false);
int _; cin >> _;
while (_--) {
int n, m;
cin >> n >> m;
vector<vector<int>> a(n, vector<int>(m));
vector<vector<int>> b(n, vector<int>(m));
for (auto &row : a)
for (auto &x : row) cin >> x;
for (auto &row : b)
for (auto &x : row) cin >> x;
vector<int> row(n), col(m);
iota(begin(row), end(row), 0);
iota(begin(col), end(col), 0);
int res = 100;
do {
do {
vector<vector<int>> c(n, vector<int>(m));
for (int i = 0; i < n; ++i)
for (int j = 0; j < m; ++j)
c[i][j] = a[row[i]][col[j]];
if (c != b) continue;
res = min(res, count(row) + count(col));
} while (next_permutation(begin(col), end(col)));
} while (next_permutation(begin(row), end(row)));
cout << (res < 100 ? res : -1) << '\n';
}
return 0;
}
复杂度分析
枚举所有的 时间复杂度为 ;
构造方格 并判断是否和 相等的时间复杂度为 ;
求 的逆序对数量时间复杂度为 ;
求 的逆序对数量时间复杂度为 ;
因此总的时间复杂度为 。
T5、照片装裱
题目解析
我们可以把所有的照片和相框的宽度和长度放在一块,并按照宽度从大到小排序,如果一个照片和一个相框的宽度相同,则将相框放在前面。
接下来准备一个容器 ,并遍历排序后的照片和相框:
- 若当前为相框 ,则将 放入 中。
- 若当前为照片 ,则从 中找最小的大于等于 的元素,并将其从 中删除。如果无法找到这样的元素,说明这个照片无法找到一个相框来装裱,答案为 。
如果成功遍历完所有的元素,答案为 。
如果使用一个普通数组充当容器 ,那么以上算法的时间复杂度为 ,但是我们可以使用 std::multiset
作为 来解决这个问题,时间复杂度为: 。
AC代码
#include <bits/stdc++.h>
using namespace std;
int main() {
cin.tie(nullptr)->sync_with_stdio(false);
int _; cin >> _;
while (_--) {
int n, m;
cin >> n >> m;
vector<pair<int, int>> a(n), b(m);
for (auto &[w, l] : a) cin >> w;
for (auto &[w, l] : a) cin >> l;
for (auto &[w, l] : b) cin >> w;
for (auto &[w, l] : b) cin >> l;
vector<tuple<int, bool, int>> arr;
for (auto &[w, l] : a)
arr.emplace_back(w, 0, l);
for (auto &[w, l] : b)
arr.emplace_back(w, 1, l);
sort(arr.rbegin(), arr.rend());
auto check = [&]() {
multiset<int> st;
for (auto &[w, t, l] : arr) {
if (t) st.insert(l);
else {
auto obj = st.lower_bound(l);
if (obj == end(st)) return false;
st.erase(obj);
}
}
return true;
};
cout << (check() ? "Yes" : "No") << '\n';
}
return 0;
}
T6、方格染色
题目解析
涂黑的一组方格与棋子移动的方法相关,因此只需计算到达每个方格的方案数即可。
若我们忽略解法的复杂度我们可以使用以下这种动态规划的方法来解决这个问题:
vector<int> dp(n);
for (int i = 0; i < n; ++i)
for (int j = i + A[i]; j < n; j += A[i])
dp[j] += dp[i];
当 较大时,对 的迭代不会重复很多次,所以这种方法似乎是合理的,但如果以 为例,则需要花费 的时间,所以这种算法无法通过本题。
考虑在 相同的情况下集体更新。具体来说,在从 开始的转换过程中,如果存在 使得 ,那么之后在 的循环过程中将它们全部相加。
时间复杂度
上述方法可以在 的时间复杂度内完成。
如果每个 在更新的过程中全部重复掉,那么对于每个 只需要将其移动一次,之后让 代替 更新。
最坏的情况是没有 "一起更新",并且 的值尽可能的小。
在这种情况下, 就像这个样子 ,而那么 的调用次数为:
即:
上式中 所以
那么我们可以由上式得到:
故整体算法时间复杂度为 。
AC代码
#include <bits/stdc++.h>
using namespace std;
constexpr int MOD = 998244353;
int main() {
cin.tie(nullptr)->sync_with_stdio(false);
int _; cin >> _;
while (_--) {
int n; cin >> n;
vector<int> arr(n);
for (auto &x : arr) cin >> x;
vector<int> dp(n), f(n);
dp[0] = 1;
for (int i = 0; i < n; ++i)
for (int j = i + arr[i]; j < n; j += arr[i]) {
dp[j] = (dp[j] + (dp[i] + f[i]) % MOD) % MOD;
if (arr[j] == arr[i]) {
f[j] = (dp[i] + f[i]) % MOD;
break;
}
}
int res = 0;
for (int i = 0; i < n; ++i)
res = (res + dp[i]) % MOD;
cout << res << '\n';
}
return 0;
}
全部评论 2
【ACGO视频题解】排位赛#5直播讲解地址:
https://www.bilibili.com/video/BV1yA4m1A71x/?share_source=copy_web&vd_source=5fbc31e4ee8b2ef652a8c255210894af2024-04-06 来自 浙江
0%%%
2024-03-10 来自 浙江
0
有帮助,赞一个