Codeforces Round 1017 (Div. 4)题解
题目地址
https://codeforces.com/contest/2094
锐评
本次题目没什么好吐槽的。题意难度都挺好的,也没什么坑,比较符合D4预期。奈何自己太菜,想的有点慢,时间不够。F题构造题是我的弱项,想到个弱解被叉了。
题解
Problem A. Trippi Troppi
题目大意
Trippi Troppi 生活在一个奇异的世界。每个国家的古代名称由三个字符串组成。每个字符串的第一个字母连起来就是该国的现代名称。
给定国家的古代名称,请输出现代名称。
题解思路:模拟
直接按照题意输出即可。时间复杂度为 \(O(1)\) 。
参考代码(C++)
#include <bits/stdc++.h>
using namespace std;
string sa, sb, sc;
void solve() {
cin >> sa >> sb >> sc;
cout << sa[0] << sb[0] << sc[0] << '\n';
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
cout.tie(nullptr);
int t = 1;
cin >> t;
while (t--)
solve();
return 0;
}
Problem B. Bobritto Bandito
题目大意
在波布里托-班迪托居住的小镇上,在一条无穷数线上有无穷多的房子,房子的位置是 \(\ldots, -2, -1, 0, 1, 2, \ldots\) 。在 \(0\) 天,他让 \(0\) 号房子里不幸的居民感染了瘟疫。接下来的每一天,瘟疫都会传播到恰好1个健康的家庭,而这些家庭恰好与一个受感染的家庭相邻。可以看出,每天受感染的房屋形成一个连续的片段。
假设从第 \(l\) 户开始到第 \(r\) 户结束的线段表示为 \([l, r]\) 。你知道在 \(n\) 天后, \([l, r]\) 段被感染了。请找出可能在第 \(m\) 天( \(m \leq n\) )被感染的线段 \([l', r']\) 。
题解思路:数学
直接判断相差了几天,左/右半段缩短即可。但是要注意, \(0\) 是一定要包含的。时间复杂度为 \(O(1)\) 。
参考代码(C++)
#include <bits/stdc++.h>
using namespace std;
int n, m, l, r;
void solve() {
cin >> n >> m >> l >> r;
int cnt = n - m;
int ct = min(r, cnt);
r -= ct;
cnt -= ct;
l += cnt;
cout << l << ' ' << r << '\n';
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
cout.tie(nullptr);
int t = 1;
cin >> t;
while (t--)
solve();
return 0;
}
Problem C. Brr Brrr Patapim
题目大意
Brr Brrr 帕塔皮姆正在努力学习提拉米苏的秘密密码,它是 \(2 \cdot n\) 个元素的排列组合。为了帮助帕塔皮姆猜测,提拉米苏给了他一个 \(n \times n\) 网格 \(G\) ,其中 \(G_{i,j}\) (或者说网格中第 \(i\) 行第 \(j\) 列中的元素)包含 \(p_{i+j}\) ,或者说排列组合中的第 \((i+j)\) 个元素。
给定这个网格,请帮助帕塔皮姆破解被遗忘的密码。可以保证这个排列是存在的,而且可以证明这个排列是唯一确定的。
\(m\) 个整数的排列是 \(m\) 个整数的序列,其中都恰好包含 \(1, 2, \ldots, m\) 一次。例如, \([1, 3, 2]\) 和 \([2, 1]\) 是排列,而 \([1, 2, 4]\) 和 \([1, 3, 2, 3]\) 不是排列。
题解思路:模拟/构造
按照题意直接模拟构造,没构造出来的位置,补充即可。时间复杂度为 \(O(n^2logn)\) (因为我用了set,其实不用,序列是唯一的,所以可降为 \(O(n^2)\) )。
PS:其实只需要按照从左上角往右走7字型,然后从第2个位置开始填充即可,第1个位置直接可以算出来(详见第二份代码)。
参考代码(C++)
复杂度为 \(O(n^2logn)\) 版本代码。
#include <bits/stdc++.h>
using namespace std;
const int maxn = 1'605;
int a[maxn][maxn], b[maxn];
int n;
void solve() {
cin >> n;
int m = n << 1;
set<int> st;
for (int i = 1; i <= m; ++i) {
st.insert(i);
b[i] = -1;
}
for (int i = 1; i <= n; ++i)
for (int j = 1; j <= n; ++j) {
cin >> a[i][j];
b[i + j] = a[i][j];
st.erase(b[i + j]);
}
for (int i = 1; i <= m; ++i)
if (b[i] == -1) {
b[i] = *st.begin();
st.erase(st.begin());
}
for (int i = 1; i <= m; ++i)
cout << b[i] << (" \n"[i == m]);
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
cout.tie(nullptr);
int t = 1;
cin >> t;
while (t--)
solve();
return 0;
}
复杂度为 \(O(n^2)\) 版本代码。
#include <bits/stdc++.h>
using namespace std;
const int maxn = 1'605;
int a[maxn];
int n;
void solve() {
cin >> n;
int m = n << 1, id = 1, sum = 0, x;
for (int i = 0; i < n; ++i)
for (int j = 0; j < n; ++j) {
cin >> x;
if (i == 0 || j == n - 1) {
a[id++] = x;
sum += x;
}
}
a[0] = (((1 + m) * m) >> 1) - sum;
for (int i = 0; i < m; ++i)
cout << a[i] << (" \n"[i == m - 1]);
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
cout.tie(nullptr);
int t = 1;
cin >> t;
while (t--)
solve();
return 0;
}
Problem D. Tung Tung Sahur
题目大意
你面前有两面鼓:一面是左鼓,一面是右鼓。敲击左边的鼓可以记录为 "L",敲击右边的鼓可以记录为 "R"。
统治这个世界的奇异力量是善变的:有时敲击一次听到了一次,有时敲击一次却听到了两次。因此,敲击左边鼓的声音可能是 "L" 或 "LL",敲击右边鼓的声音可能是 "R" 或 "RR"。
敲击的顺序记录在字符串 \(p\) 中,听到的声音记录在字符串 \(s\) 中。给定 \(p\) 和 \(s\) ,判断字符串 \(s\) 是否可能是字符串 \(p\) 敲击的结果。
例如,如果 $p = $ "LR",那么敲击的结果可能是 "LR"、"LRR"、"LLR" 和 "LLRR" 中的任何一个,但字符串 "LLLR" 或 "LRL" 则不可能。
题解思路:双指针
按照题意,对于 \(p\) 中每个 "L"/"R" , \(s\) 中必须有至少一个、至多两个同样的字符与之对应。因此我们只需要遍历 \(p\) ,然后看 \(s\) 中是否有与之对应的字符满足条件即可。因为 \(p\) 中 "L" 不可能对应 \(s\) 中的 "R" ,反之亦然,因此我们每次判断连续相同的字符段即可。时间复杂度为 \(O(n + m)\) 。
参考代码(C++)
#include <bits/stdc++.h>
using namespace std;
const int maxn = 1'605;
string p, str;
int n, m;
void solve() {
cin >> p >> str;
int n = p.size(), m = str.size();
int i = 0, j = 0;
while (i < n) {
if (j == m || p[i] != str[j]) {
cout << "NO\n";
return;
}
int k1 = i + 1, k2 = j + 1;
while (k1 < n && p[k1] == p[i])
++k1;
while (k2 < m && str[k2] == str[j])
++k2;
int ca = k1 - i, cb = k2 - j;
if (ca > cb || cb > (ca << 1)) {
cout << "NO\n";
return;
}
i = k1;
j = k2;
}
cout << (j == m ? "YES\n" : "NO\n");
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
cout.tie(nullptr);
int t = 1;
cin >> t;
while (t--)
solve();
return 0;
}
Problem E. Boneca Ambalabu
题目大意
博尼卡-安巴拉布给你一个由 \(n\) 个整数 \(a_1, a_2, \ldots, a_n\) 组成的序列。
请输出所有 \(1 \leq k \leq n\) 中 \((a_k \oplus a_1) + (a_k \oplus a_2) + \ldots + (a_k \oplus a_n)\) 的最大值。注意 \(\oplus\) 表示 bitwise XOR 运算。
题解思路:枚举+位运算
根据题目给定计算公式以及 \(\oplus\) 性质,我们只需要知道,在二进制状态下,某一位的结果与其对应的其他数该位情况相关,并且与该位分别有多少个数相关,有多少个数答案就是几倍。因为我们只需要计数每一位对应多少个数,然后枚举序列每个数,计算得到的结果取最大即可。时间复杂度为 \(O(n)\) (实际为 \(30n\) ,忽略常数,但运行时间需要把 \(30\) 考虑在内)。
参考代码(C++)
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
const int maxn = 200'005;
int a[maxn], cnt[30];
int n;
void solve() {
cin >> n;
for (int i = 0; i < 30; ++i)
cnt[i] = 0;
for (int i = 0; i < n; ++i) {
cin >> a[i];
for (int j = 0; j < 30; ++j)
if ((a[i] >> j) & 1)
++cnt[j];
}
ll ans = 0;
for (int i = 0; i < n; ++i) {
ll res = 0;
for (int j = 0; j < 30; ++j) {
int mask = (a[i] >> j) & 1;
if (mask)
res += (1LL << j) * (n - cnt[j]);
else
res += (1LL << j) * cnt[j];
}
ans = max(ans, res);
}
cout << ans << '\n';
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
cout.tie(nullptr);
int t = 1;
cin >> t;
while (t--)
solve();
return 0;
}
Problem F. Trulimero Trulicina
题目大意
楚里西纳给出整数 \(n\) 、 \(m\) 和 \(k\) 。可以保证 \(k \geq 2\) 和 \(n \cdot m \equiv 0 \pmod{k}\) 。
输出一个 \(n\) 乘以 \(m\) 的整数网格,使得下列条件均成立:
- 网格中的每个整数都在 \(1\) 和 \(k\) 之间(包括首尾两个整数)。
- 从 \(1\) 到 \(k\) 的每个整数出现的次数相同。
- 共享边的两个单元格中没有相同的整数。
可以证明这样的网格总是存在的。如果有多个解,请输出任意一个。
题解思路:构造
我们会想当然地采取循环形式,如 \([1, 2, \ldots, k - 1, k, 1, 2, \ldots, k - 1, k, 1, 2, \ldots, k - 1, k]\) ,然后从左往右从上往下依次填入网格。这样,网格左右肯定不同,相差1个数。那么上下是否相同呢?假如上面为 \(x\) ,那么根据上面的填充方式,下面为 \(y = (x + m) \bmod k\) ,那么只要 \(m \bmod k\) 不等于零,那么 \(y\) 肯定在 \(x\) 右侧,上下也是不同的,符合题意。但如果恰好等于零,怎么办呢?这种情况相当于每行都是序列 \([1, 2, \ldots, k - 1, k]\) 重复 \(\frac{m}{k}\) 次,即每一行完全一样。既然这样,我把第2行往左边循环移位一个位置,那么不就刚好错开了吗?是的,这样上下也就相差了1个数。以此类推,第4,6,8等等偶数行也错开不就满足条件了。
综上所述,问题得解。时间复杂度为 \(O(nm)\) 。
参考代码(C++)
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
const int maxn = 200'005;
vector<int> adj[maxn];
int n, m, k;
void calc(int n, int m, int k) {
bool f = m % k == 0;
int cur = 0;
for (int i = 0; i < n; ++i) {
adj[i].clear();
if (f) {
if (i & 1)
cur = 1;
else
cur = 0;
}
for (int j = 0; j < m; ++j) {
adj[i].push_back(cur + 1);
cur = (cur + 1) % k;
}
}
for (int i = 0; i < n; ++i)
for (int j = 0; j < m; ++j)
cout << adj[i][j] << (" \n"[j == m - 1]);
}
void solve() {
cin >> n >> m >> k;
calc(n, m, k);
}
bool check(int n, int m) {
for (int i = 0; i < n; ++i)
for (int j = 0; j < m; ++j) {
if (i && adj[i][j] == adj[i - 1][j])
return true;
if (j && adj[i][j] == adj[i][j - 1])
return true;
}
return false;
}
void test() {
for (int i = 1; i < 30; ++i)
for (int j = 1; j < 30; ++j)
for (int k = 2; k * k <= i * j; ++k)
if (i * j % k == 0) {
calc(i, j, k);
if (check(i, j))
cout << i << ' ' << j << ' ' << k << '\n';
}
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
cout.tie(nullptr);
// test();
int t = 1;
cin >> t;
while (t--)
solve();
return 0;
}
Problem G. Chimpanzini Bananini
题目大意
奇潘齐尼-巴纳尼尼站在了一场重大战役的边缘,这场战役注定会带来最终的胜利。
对于长度为 \(m\) 的任意数组 \(b\) ,我们把数组的粗糙度记为 \(\sum_{i=1}^m{b_i \cdot i} = b_1 \cdot 1 + b_2 \cdot 2 + b_3 \cdot 3 + \ldots + b_m \cdot m\) 。
Chimpanzini Bananini 送给你一个空数组。你可以对它进行三种操作。
- 对数组进行循环移位。也就是说,数组 \([a_1, a_2, \ldots, a_n]\) 变成 \([a_n, a_1, a_2, \ldots, a_{n-1}]\) 。
- 反转整个数组。即数组 \([a_1, a_2, \ldots, a_n]\) 变成 \([a_n, a_{n-1}, \ldots, a_1]\) 。
- 在数组末尾添加一个元素。在数组的末尾添加 \(k\) 之后,数组 \([a_1, a_2, \ldots, a_n]\) 变为 \([a_1, a_2, \ldots, a_n, k]\) 。
每次操作后,您都想计算数组的粗糙度。
请注意,所有操作都是持久的。这意味着每次操作都会修改数组,后续操作应在前一次操作后应用于数组的当前状态。
题解思路:双端队列+数学
我们来一个操作一个操作地分析。我们先假设操作前的粗糙度为 \(f(n) = d\) ,对于操作一,操作后粗糙度如下。
化简后可得。
看起来我们只需要知道当前数组的和 \(sum\) 、最后一个元素 \(a_n\) 以及粗糙度 \(d\) 就能快速计算出 \(f'(n) = d + sum - a_n \cdot n\) 。那么支持从尾部删除和头部插入的数据结构用什么呢?显然,双端队列即可。
对于操作二,翻转整个数组每次都要 \(O(n)\) 时间复杂度,显然不可接受。那么怎么办呢?基于操作一的灵感,我们倒着维护一份一模一样的双端队列是不是可以呢?显然是可行的,这样当翻转时,我们切换一下当前双端队列即可,只需要 \(O(1)\) 时间复杂度,可以接受。那么对于操作一,此队列(形如 \([a_n, a_{n - 1}, \cdots, a_2, a_1]\) )的粗糙度如何变化呢?我们假设操作前的粗糙度为 \(g(n) = d'\) 、当前数组的和为 \(sum\) (和肯定是一样的),那么操作一相当于把这里的 \(a_n\) 放到队列的尾部,操作后粗糙度如下。
化简后可得。
对于操作三,添加一个元素 \(x\) ,相当于往正向队列尾部添加一个元素,往逆向队列头部添加一个元素。根据上面分析,正向队列粗糙度为 \(f(n)\) 、逆向队列粗糙度为 \(g(n)\) 以及数组元素和为 \(sum\) ,那么有如下公式。
操作后,有如下公式。
综上所述,我们按照分析模拟即可。以上每个操作时间复杂度均为 \(O(1)\) ,因此整体时间复杂度为 \(O(q)\) 。
参考代码(C++)
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
const int maxn = 100'005;
int q;
void solve() {
cin >> q;
deque<int> dq[2];
ll sum = 0, ans[2] = {0};
int cur = 0, op, x;
while (q--) {
cin >> op;
if (op == 3) {
cin >> x;
dq[cur].push_back(x);
sum += x;
ans[cur] += 1LL * dq[cur].size() * x;
dq[cur ^ 1].push_front(x);
ans[cur ^ 1] += sum;
} else if (op == 2)
cur ^= 1;
else if (!dq[cur].empty()) {
int siz = dq[cur].size();
x = dq[cur].back();
// cout << "siz:" << siz << "; x:" << x << '\n';
dq[cur].pop_back();
dq[cur].push_front(x);
ans[cur] += sum - 1LL * siz * x;
dq[cur ^ 1].pop_front();
dq[cur ^ 1].push_back(x);
ans[cur ^ 1] += 1LL * siz * x - sum;
}
cout << ans[cur] << '\n';
}
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
cout.tie(nullptr);
int t = 1;
cin >> t;
while (t--)
solve();
return 0;
}
Problem H. La Vaca Saturno Saturnita
题目大意
Saturnita 的情绪取决于一个长度为 \(n\) 的数组 \(a\) 和一个只有他知道如何计算的函数 \(f(k, a, l, r)\) 。下面是他的函数 \(f(k, a, l, r)\) 的伪代码。
int ans = 0;
for (int i = l; i <= r; ++i) {
while (k % a[i] == 0)
k /= a[i];
ans += k;
}
cout << ans << '\n';
给你 \(q\) 个查询,每个查询都包含整数 \(k\) 、 \(l\) 和 \(r\) 。请为每个查询输出 \(f(k, a, l, r)\) 。
题解思路:枚举+二分+数论
注意此题时限是4s。由于 \(1 \leq q \leq 5 \cdot 10^4\) 、 \(1 \leq k \leq 10^5\) ,显然我们依据数论知识可以用时间复杂度为 \(O(\sqrt{k})\) 的方法算出 \(k\) 的所有因子。
观察上述伪代码,对于每个因子,其会一直除 \(k\) ,直到除不尽。那么对于区间 \([l, r]\) 内处理后的 \(k\) 是怎么样的呢?它一定是形如 \([k1, k1, \ldots, k2, k2, \ldots, k_m, k_m, \ldots]\) 这样的。那么什么时候 \(k\) 才会变化呢?显然遇到一个因子就会变化。因此,我们只需要找到区间 \([l, r]\) 内所有的因子,按位置从小到大枚举出所有的 \(k_i\) 即可,只需要记录上一个位置在哪里即可得到段的长度 \(l_i\) ,答案就是 \(\sum_{i = 1}^m{k_i \cdot l_i}\) 。
根据上面的分析,因子很容易计算,那么怎么找到合适的因子呢?我们只需要把题目给定因子的所有位置按照从小到大顺序存起来,就可以用二分来查找处于区间 \([l, r]\) 的首个位置。
综上所述,问题得解。时间复杂度为 \(O(q \sqrt{k} logn)\) 。
参考代码(C++)
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
const int maxn = 100'005;
int a[maxn];
int n, q;
void solve() {
cin >> n >> q;
map<int, vector<int>> adj;
for (int i = 0; i < n; ++i) {
cin >> a[i];
adj[a[i]].push_back(i);
}
int k, l, r;
while (q--) {
cin >> k >> l >> r;
--l, --r;
if (k == 1)
cout << (r - l + 1) << '\n';
else {
vector<int> vi, vp;
for (int i = 2; i * i <= k; ++i)
if (k % i == 0) {
vi.push_back(i);
int j = k / i;
if (j != i)
vi.push_back(j);
}
vi.push_back(k);
for (int& x : vi)
if (adj.count(x)) {
auto& v = adj[x];
auto it = lower_bound(v.begin(), v.end(), l);
if (it != v.end() && *it <= r)
vp.push_back(*it);
}
sort(vp.begin(), vp.end());
ll ans = 0;
int last = l;
for (int& id : vp) {
int x = k;
while (x % a[id] == 0)
x /= a[id];
int cnt = id - last;
ans += 1LL * cnt * k;
last = id;
k = x;
}
ans += 1LL * k * (r + 1 - last);
cout << ans << '\n';
}
}
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
cout.tie(nullptr);
int t = 1;
cin >> t;
while (t--)
solve();
return 0;
}
posted on 2025-04-14 20:16 Silenceneo 阅读(220) 评论(0) 收藏 举报