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个数。以此类推,第468等等偶数行也错开不就满足条件了。

综上所述,问题得解。时间复杂度为 \(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 送给你一个空数组。你可以对它进行三种操作。

  1. 对数组进行循环移位。也就是说,数组 \([a_1, a_2, \ldots, a_n]\) 变成 \([a_n, a_1, a_2, \ldots, a_{n-1}]\)
  2. 反转整个数组。即数组 \([a_1, a_2, \ldots, a_n]\) 变成 \([a_n, a_{n-1}, \ldots, a_1]\)
  3. 在数组末尾添加一个元素。在数组的末尾添加 \(k\) 之后,数组 \([a_1, a_2, \ldots, a_n]\) 变为 \([a_1, a_2, \ldots, a_n, k]\)

每次操作后,您都想计算数组的粗糙度。

请注意,所有操作都是持久的。这意味着每次操作都会修改数组,后续操作应在前一次操作后应用于数组的当前状态。

题解思路:双端队列+数学

我们来一个操作一个操作地分析。我们先假设操作前的粗糙度为 \(f(n) = d\) ,对于操作一,操作后粗糙度如下。

\[f'(n) = a_n \cdot 1 + a_1 \cdot 2 + a_2 \cdot 3 + \ldots + a_{n - 1} \cdot n = a_n \cdot 1 + a_1 + a_2 + \cdots + a_{n - 1} + a_1 \cdot 1 + a_2 \cdot 2 + \ldots + a_{n - 1} \cdot (n - 1) \]

化简后可得。

\[f'(n) = \sum_{i = 1}^{n - 1}{a_i} + d - a_n \cdot (n - 1) \]

看起来我们只需要知道当前数组的和 \(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\) 放到队列的尾部,操作后粗糙度如下。

\[g'(n) = a_{n - 1} \cdot 1 + \cdots + a_2 \cdot (n - 2) + a_1 \cdot (n - 1) + a_n \cdot n = a_{n - 1} \cdot 2 + \cdots + a_2 \cdot (n - 1) + a_1 \cdot n - a_{n - 1} - \cdots - a_2 - a_1 + a_n \cdot n \]

化简后可得。

\[g'(n) = d' - a_n \cdot 1 - \sum_{i = 1}^{n - 1}{a_i} + a_n \cdot n = d' + a_n \cdot n - sum \]

对于操作三,添加一个元素 \(x\) ,相当于往正向队列尾部添加一个元素,往逆向队列头部添加一个元素。根据上面分析,正向队列粗糙度为 \(f(n)\) 、逆向队列粗糙度为 \(g(n)\) 以及数组元素和为 \(sum\) ,那么有如下公式。

\[f(n) = a_1 \cdot 1 + a_2 \cdot 2 + \cdots + a_n \cdot n \]

\[g(n) = a_n \cdot 1 + \cdots + a_2 \cdot (n - 1) + a_1 \cdot n \]

\[sum = a_1 + a_2 + \cdots + a_n \]

操作后,有如下公式。

\[f'(n) = a_1 \cdot 1 + a_2 \cdot 2 + \cdots + a_n \cdot n + x \cdot (n + 1) = f(n) + x \cdot (n + 1) \]

\[g'(n) = x \cdot 1 + a_n \cdot 2 + \cdots + a_2 \cdot n + a_1 \cdot (n + 1) = g(n) + sum + x \]

\[sum' = sum + x \]

综上所述,我们按照分析模拟即可。以上每个操作时间复杂度均为 \(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)    收藏  举报

导航