官方题解|巅峰赛#16
2024-12-23 11:04:04
发布于:浙江
ACGO 巅峰赛#16 题解
写在前面
本场排位赛的所有题目的难度评级为:
题目编号 | 题目标题 | 难度 |
---|---|---|
A. | 美丽数 | 入门 |
B. | 环形回文串 | 入门 |
C. | 无重复字符的子串 | 普及- |
D. | 统计满足条件的子串个数 | 普及+/提高 |
E. | 考拉兹猜想 | 普及+/提高 |
F. | 帕罗蒂克 | 普及+/提高 |
非常感谢大家参加本场巅峰赛!
题解简述
以下提供每道题目的思路简述,详细题解的AC代码包括复杂度分析,请点击题目标题查看。
A - 美丽数
模拟
我们可以使用一个数组 cnt
来统计 中各个数字出现的次数。
然后枚举 中每个数字进行检查,若 中存在数字 ,且 ,那么说明不是 美丽数,直接输出 No
,并结束程序;否则循环结束后,没有退出程序,则输出 Yes
。
#include <bits/stdc++.h>
int main() {
int n; std::cin >> n;
int cnt[10]{};
while (n > 0) {
cnt[n % 10] += 1;
n /= 10;
}
for (int i = 1; i < 10; ++i)
if (cnt[i] and cnt[i] != i) {
std::cout << "No\n";
return 0;
}
std::cout << "Yes\n";
return 0;
}
B - 环形回文串
枚举
我们考虑将 个原字符串 ,前后拼接起来构造为字符串 。
然后枚举下标 。检查在字符串 中,以 开头的长度为 的子串是否为回文串即可。
#include <bits/stdc++.h>
bool check(const std::string &s) {
int n = s.size();
for (int i = 0; i < n / 2; ++i)
if (s[i] != s[n - 1 - i]) return false;
return true;
}
int main() {
std::string s;
std::cin >> s;
int n = s.size();
s = s + s;
for (int i = 0; i < n; ++i)
if (check(s.substr(i, n))) {
std::cout << "Yes\n";
return 0;
}
std::cout << "No\n";
return 0;
}
C - 无重复字符的子串
模拟;枚举
由于不含重复字符的字符串最长为 ,所以我们可以暴力枚举每个起点为 的子串,并使用数组 cnt
来统计每一个字符出现的次数,若在向后遍历的过程中,发现某个字符已经出现过,则停止向后遍历。
#include <bits/stdc++.h>
int main() {
std::cin.tie(nullptr)->sync_with_stdio(false);
std::string s; std::cin >> s;
int n = s.size(), res = 0;
for (int i = 0; i < n; ++i) {
int cnt[26]{};
for (int j = i; j < n; ++j) {
if (++cnt[s[j] - 'a'] > 1) break;
res += 1;
}
}
std::cout << res << '\n';
return 0;
}
D - 统计满足条件的子串个数
数据结构(树状数组,线段树,平衡树);
使用「树状数组」等数据结构,ft[i][j]
维护字符串 中字符 在前 个字符中出现的次数。
使用树状数组,预处理 cnt[i][j]
数组,表示字符串 S
中,以 开头,以 结尾的子串的个数。
对于每次修改操作,我们考虑删除字符 对以 开头以 结尾和以 开头以 结尾的子串数量,即 和 的影响。
那么我们直接枚举 :
- 对于 ,查询前 个字符中,字符 出现的次数,并将其从 减去。
- 对于 ,查询第 个及以后的字符中,字符 出现的次数,并将其从 减去。
同时修改树状数组。
然后将 修改为字符 ,使用与计算删除的影响相同的做法,即可得到修改后对 cnt
数组的影响。
对于每个查询,我们可以以 的时间从 cnt
数组中得到答案。
令 为字符串 的长度, 为字符集的大小,整个算法,预处理 cnt
数组的时间复杂度为 。处理每个查询的时间复杂度为 ,整个算法时间复杂度为 。
#include <bits/stdc++.h>
using i64 = long long;
// class FenwickTree {...}
int main() {
std::cin.tie(nullptr)->sync_with_stdio(false);
int n, q;
std::cin >> n >> q;
std::string s; std::cin >> s;
std::vector<FenwickTree<int>> ft(26, FenwickTree<int>(n));
for (int i = 0; i < n; ++i)
ft[s[i] - 'a'].add(i, 1);
i64 cnt[26][26]{};
for (int i = 0; i < n; ++i)
for (int j = 0; j < 26; ++j)
cnt[s[i] - 'a'][j] += ft[j].sum(i, n);
while (q--) {
int t; std::cin >> t;
if (t == 1) {
int x; std::cin >> x, --x;
char c; std::cin >> c;
for (int i = 0; i < 26; ++i) {
cnt[i][s[x] - 'a'] -= ft[i].sum(0, x);
cnt[s[x] - 'a'][i] -= ft[i].sum(x, n);
}
ft[s[x] - 'a'].add(x, -1);
s[x] = c;
ft[s[x] - 'a'].add(x, +1);
for (int i = 0; i < 26; ++i) {
cnt[i][s[x] - 'a'] += ft[i].sum(0, x);
cnt[s[x] - 'a'][i] += ft[i].sum(x, n);
}
}
else {
char a, b;
std::cin >> a >> b;
std::cout << cnt[a - 'a'][b - 'a'] << '\n';
}
}
return 0;
}
E - 考拉兹猜想
数据结构(平方分割,等);
编写程序计算, 内变为 ,需要操作次数最多的数为 ,经过 次操作后变为 。这意味着,数组中的每个元素最多经过 次操作后,后续的操作对其失效。
我们考虑将数组分割为 个部分,对于每个块,我们维护一个容器,存储块内不为 的元素的下标,另外维护一个变量维护块内为 的元素数量。每次修改操作,:
- 对于未整体出现在同一个块内的元素,暴力更新。
- 对于整体出现在同一个块内的元素,只修改块内不为 的元素,同时将修改后所有的变为 的元素下标,从块内容器中删除,同时令块内为 的元素数量增加。
由于每个元素最多被修改 次后,变为 ,上述操作,第一部分时间复杂度为 ,第二部分同样为 。
对于每个查询 ,也分为两个部分:
- 对于未整体出现在同一个块内的元素,暴力统计。
- 对于整体出现在同一个块内的元素,查询块内维护的块内为 的元素数量。
两部分操作时间复杂度皆为 。
#include <bits/stdc++.h>
using i64 = long long;
template<class T>
class SquareRoot {
private:
int n, len, cnt;
std::vector<T> v;
std::vector<T> sum;
std::vector<std::vector<int>> block;
std::vector<int> id;
std::vector<std::vector<int>> rid;
public:
SquareRoot() : SquareRoot(0) {}
explicit SquareRoot(int n) : SquareRoot(std::vector<int>(n)) {}
explicit SquareRoot(const std::vector<T> &a) : n(a.size()), v(a) {
len = std::sqrt(n);
cnt = (n + len - 1) / len;
sum = std::vector<T>(cnt);
block = std::vector<std::vector<int>>(cnt);
id = std::vector<int>(n);
rid = std::vector<std::vector<int>>(cnt);
for (int i = 0; i < n; ++i) {
id[i] = i / len;
rid[id[i]].push_back(i);
if (v[i] > 1)
block[id[i]].push_back(i);
else
sum[id[i]] += 1;
}
}
bool singalUpdate(int i) {
if (v[i] == 1) return true;
if (v[i] & 1)
v[i] = v[i] * 3 + 1;
else
v[i] /= 2;
if (v[i] == 1) sum[id[i]] += 1;
return v[i] == 1;
}
void blockUpdate(int i) {
std::vector<int> st;
for (auto &j : block[i])
if (!singalUpdate(j))
st.push_back(j);
block[i] = std::move(st);
}
void apply(int l, int r) {
if (l >= r) return;
int sid = id[l], eid = id[r - 1];
if (sid == eid) {
for (int i = l; i < r; ++i)
singalUpdate(i);
return;
}
for (int i = l; id[i] == sid; ++i)
singalUpdate(i);
for (int i = sid + 1; i < eid; ++i)
blockUpdate(i);
for (int i = r - 1; id[i] == eid; --i)
singalUpdate(i);
}
T prod(int l, int r) {
if (l >= r) return 0;
T sm = 0;
int sid = id[l], eid = id[r - 1];
if (sid == eid) {
for (int i = l; i < r; ++i)
sm += v[i] == 1;
return sm;
}
for (int i = l; id[i] == sid; ++i)
sm += v[i] == 1;
for (int i = sid + 1; i < eid; ++i)
sm += sum[i];
for (int i = r - 1; id[i] == eid; --i)
sm += v[i] == 1;
return sm;
}
};
int main() {
std::cin.tie(nullptr)->sync_with_stdio(false);
int n, q;
std::cin >> n >> q;
std::vector<int> a(n + 1);
for (int i = 1; i <= n; ++i)
std::cin >> a[i];
SquareRoot<int> data(a);
while (q--) {
int t, l, r;
std::cin >> t >> l >> r;
if (t == 1)
data.apply(l, r + 1);
else
std::cout << data.prod(l, r + 1) << '\n';
}
return 0;
}
F - 帕罗蒂克
状态压缩动态规划
定义 为第 位朋友,已经经过的站点集合为 ,此时位于车站 的可行性。
那么我们就可以对每一位朋友分开考虑,并使用状态压缩动态规划,在 的时间复杂度内,计算出上述 数组。
然后我们定义 表示,经过 次移动,位于车站 的朋友的集合。根据已经得出的 数组,我们可以很容易计算出 数组。
最后根据 中的存储的集合是否为所有朋友的全集,来判断,是否可以在站点 汇合即可。
#include <bits/stdc++.h>
constexpr int N = 15;
int main() {
std::cin.tie(nullptr)->sync_with_stdio(false);
int n, m, k;
std::cin >> n >> m >> k;
std::vector<int> a(k);
for (auto &x : a) std::cin >> x, --x;
std::vector<std::vector<int>> g(n);
for (int i = 0; i < m; ++i) {
int u, v;
std::cin >> u >> v, --u, --v;
g[u].push_back(v);
g[v].push_back(u);
}
int dp[N][1 << N][N]{};
for (int i = 0; i < k; ++i)
dp[i][1 << a[i]][a[i]] = 1;
for (int i = 0; i < k; ++i)
for (unsigned mask = 0; mask < (1u << n); ++mask)
for (int j = 0; j < n; ++j) if (dp[i][mask][j])
for (auto &nj : g[j]) if (~mask >> nj & 1)
dp[i][mask | 1 << nj][nj] = 1;
int turn[N + 1][N]{};
for (int i = 0; i < k; ++i)
for (unsigned mask = 0; mask < (1u << n); ++mask)
for (int j = 0; j < n; ++j) if (dp[i][mask][j])
turn[__builtin_popcount(mask)][j] |= 1 << i;
for (int step = 0; step <= n; ++step)
for (int i = 0; i < n; ++i)
if (turn[step][i] == (1 << k) - 1) {
std::cout << i + 1 << '\n';
return 0;
}
std::cout << -1 << '\n';
return 0;
}
全部评论 2
这么难?
2024-12-28 来自 浙江
0顶
2024-12-16 来自 广东
0
有帮助,赞一个