Codeforces Round #879 (Div. 2) A-F

比赛链接

A

代码

#include <bits/stdc++.h>
using namespace std;
using ll = long long;

bool solve() {
    int n;
    cin >> n;
    int cnt = 0;
    for (int i = 1;i <= n;i++) {
        int x;
        cin >> x;
        if (x == -1) cnt++;
    }
    int diff = max(0, (2 * cnt - n + 1) / 2);
    cout << diff + ((cnt - diff) % 2 == 1) << '\n';
    return true;
}

int main() {
    std::ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
    int t = 1;
    cin >> t;
    while (t--) {
        if (!solve()) cout << -1 << '\n';
    }
    return 0;
}

B

代码

#include <bits/stdc++.h>
using namespace std;
using ll = long long;

bool solve() {
    string a, b;
    cin >> a >> b;
    int n = max(a.size(), b.size());
    a = "?" + string(n - a.size(), '0') + a;
    b = "?" + b;
    int ans = 0;
    for (int i = 1;i <= n;i++) {
        if (a[i] != b[i]) {
            ans += b[i] - a[i] + 9 * (n - i);
            break;
        }
    }
    cout << ans << '\n';
    return true;
}

int main() {
    std::ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
    int t = 1;
    cin >> t;
    while (t--) {
        if (!solve()) cout << -1 << '\n';
    }
    return 0;
}

C

题意

给两个串 \(S,T\) ,AB轮流操作,A先操作。

A:选一个串,选择其中一个位置,修改成任意想要的字符。

B:选择一个串,反转他。

问最少操作几次,使得 \(S=T\)

题解

知识点:贪心。

显然,B不会修改字符,并且操作两次等于没操作。

因此,关键在于A的两种情况的操作次数:

  1. 把两个字符串的对应位置修改成一样。
  2. 反转其中一个后,把两个字符串的对应位置修改成一样。

这两种情况,对B的要求:

  1. 前者需要B操作偶数次,因此若A的操作次数是奇数,那么B要减一次操作。
  2. 后者需要B操作奇数次,因此若A的操作次数是偶数,那么B要减一次操作。注意这种A可能操作 \(0\) 次,此时通过前面计算得出的是 \(-1\) 是不合法的,为了方便我们对前面的结果与 \(2\) 取最大值即可。

时间复杂度 \(O(n)\)

空间复杂度 \(O(n)\)

代码

#include <bits/stdc++.h>
using namespace std;
using ll = long long;

bool solve() {
    int n;
    cin >> n;
    string s, t;
    cin >> s >> t;
    s = "?" + s;
    t = "?" + t;
    int cnt1 = 0, cnt2 = 0;
    for (int i = 1;i <= n;i++) {
        cnt1 += s[i] == t[i];
        cnt2 += s[i] == t[n - i + 1];
    }
    int ans1 = 2 * (n - cnt1) - ((n - cnt1) & 1);
    int ans2 = max(2, 2 * (n - cnt2) - (!((n - cnt2) & 1)));
    cout << min(ans1, ans2) << '\n';
    return true;
}

int main() {
    std::ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
    int t = 1;
    cin >> t;
    while (t--) {
        if (!solve()) cout << -1 << '\n';
    }
    return 0;
}

D

题意

\([1,m]\) 上给定 \(n\) 个区间 \([l_i,r_i]\)

可以选择 \([1,m]\) 中若干个不同数字,那么每个区间的值为区间出现的所选数字个数减去剩余的所选数字个数。

问如何选择数字,使得选择后区间值的最大值与最小值的差值最大。

题解

知识点:枚举,贪心。

考虑枚举每个区间作为最大值,对于一个区间 \(A\) 作为最大值,即选择其中所有数字。此时,对于其他任意一个区间 \(B\)\(A\)\(B\) 的区间值的差值为 \(|A| - (|A \cap B|- (|A|-|A \cap B|)) = 2(|A| - |A \cap B|)\) ,等价于我们要选择一个 \(B\) ,最大化 \(A\) 不在 \(B\) 中的部分,考虑三种情况以及对应的方案:

  1. \(B\)\(A\) 中或 \(A\)\(B\) 中,我们要找到最短的 \(|B|_{min}\) ,则差值最大值为 \(|A| - |B|\)
  2. \(B\)\(A\) 的左侧相交,我们要找到最小的右端点 \(minR\) ,则差值最大值为 \(r_i - minR\)
  3. \(B\)\(A\) 的右侧相交,我们要找到最大的左端点 \(maxL\),则差值最大值 \(maxL - l_i\)

更进一步,我们可以得到,对于一个 \(B\) ,无论 \(B\) 属于上面哪一种情况,三种方案只会有一个会得到最大值。因此, \(A\)\(B\) 的关系并不重要,我们不需要每次对一个特定的 \(A\) 将所有的 \(B\) 按三种情况分类后分别求解,而是可以无视 \(A\) 是什么,直接对所有 \(B\) 采取三种方案,其中总会有一个正确的最大值。

既然如此,那么我们事先得到所有区间的最短长度、最小右端点、最大左端点,随后枚举每个区间作为最大值区间 \(A\) ,直接使用它们计算答案取最大值即可,如此会方便很多。

时间复杂度 \(O(n)\)

空间复杂度 \(O(n)\)

代码

#include <bits/stdc++.h>
using namespace std;
using ll = long long;

int L[100007], R[100007];
bool solve() {
    int n, m;
    cin >> n >> m;
    int minlen = 2e9, maxlen = 0;
    for (int i = 1;i <= n;i++) {
        cin >> L[i] >> R[i];
        minlen = min(minlen, R[i] - L[i] + 1);
        maxlen = max(maxlen, R[i] - L[i] + 1);
    };
    int maxL = *max_element(L + 1, L + n + 1);
    int minR = *min_element(R + 1, R + n + 1);
    int ans = maxlen - minlen;
    for (int i = 1;i <= n;i++) ans = max(ans, min(R[i] - L[i] + 1, max(maxL - L[i], R[i] - minR)));
    cout << 2 * ans << '\n';
    return true;
}

int main() {
    std::ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
    int t = 1;
    cin >> t;
    while (t--) {
        if (!solve()) cout << -1 << '\n';
    }
    return 0;
}

E

题意

给一个有 \(n\) 个数的数组 \(a\) ,问最小的 \(x\) 使得 \(x\) 不等于 \(a\) 任意子区间的 \(\text{lcm}\)

题解

知识点:GCD和LCM,线性dp,枚举。

问题等价于找到全部子区间LCM的MEX,即第一个未出现的数字。

全部子区间的LCM种类不会超过 \(n^2\) 个,因此其MEX大小不会超过 \(n^2\) ,我们只需要求出不大于 \(n^2\) 的LCM即可,超过的 \(n^2\) 的LCM可以断定是无效的。

我们考虑固定子区间右端点,对于任意左端点,设子区间产生不同的LCM为 \(x_1 < \cdots < x_k\) 。同时,因为是固定了右端点,因此大的子区间的LCM一定是小的子区间的LCM的倍数,有不等式 \(x_i \geq 2x_{i-1}, i = 2,3,\cdots,k\) ,于是我们有 \(n^2 \geq x_k \geq 2^{k-1}\) ,所以 \(k\leq 1+ 2\log_2n\)

通过上述方法,我们可以得到每次迭代中,枚举的LCM不超过 \(1+ 2\log_2n\) ,总的LCM不超过 \(n(1+2\log_2n)\) 。因此,我们这个方法得到全部有效的LCM的复杂度是 \(O(n \log n)\) 的。当然,我们需要用 set 维护不同种类,所以最终复杂度是 \(O(n \log^2 n)\)

更进一步地,LCM总种类数 \(n(1+2\log_2n)\) 又可以反过来推断出MEX大小的不会超过 \(n(1+2\log_2n)\) ,这个大小在这道题不超过 \(2 \times 10^7\) ,因此可以用一个 int 范围的数限制大小。

时间复杂度 \(O(n \log^2 n)\)

空间复杂度 \(O(n \log n)\)

代码

#include <bits/stdc++.h>
using namespace std;
using ll = long long;

int a[300007];
bool solve() {
    int n;
    cin >> n;
    for (int i = 1;i <= n;i++) cin >> a[i];

    set<int> st, pre;
    for (int i = 1;i <= n;i++) {
        set<int> now;
        now.insert(a[i]);
        for (auto x : pre) {
            ll y = lcm((ll)x, a[i]);
            if (y <= 2e6) now.insert(y);
        }
        for (auto x : now) st.insert(x);
        swap(pre, now);
    }
    int ans = 1;
    while (st.count(ans)) ans++;
    cout << ans << '\n';
    return true;
}

int main() {
    std::ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
    int t = 1;
    cin >> t;
    while (t--) {
        if (!solve()) cout << -1 << '\n';
    }
    return 0;
}

F

题意

给一个排列数组 \(p\)\(n\) 个数字,有一个可以移动的容器在数组上方,只能存放一个数字,一开始在 \(a_1\) 上方,容器有如下操作:

  1. 如果容器是空的,将容器下方的数字拿到容器里(如果该位置有数字)。
  2. 如果容器下方是空的,将容器中的数字放到这个位置(如果容器有数字)。
  3. 如果容器和下方都有数字,交换两者数字。
  4. 容器向右移动一个位置。
  5. 容器回到 \(a_1\) 上方。

给出 \(q\) 个询问,每个询问会先改变数组:

  1. 数组循环左移 \(k\) 位。
  2. 数组循环右移 \(k\) 位。
  3. 数组反转。

在第一个询问之前和每个询问后,要回答当前数组最少需要用操作5几次,才能使得 \(p\) 是递增的。(回答询问时,不需要改变数组)

题解

知识点:枚举,差分,数学。

首先是一个结论,对于一个排列,操作的次数等于 \(i > a_i\) 的位置数。

考虑构造 \(i \to a_i\) 的图,找到第一个 \(i \neq a_i\) 的位置,将其数字 \(a_i\) 拿出移动到 \(a_{a_i}\) 并交换,以此类推。若遇到 \(i<a_i\) 就必须使用一次操作5,因此最后答案是 \(i>a_i\) 的位置数。

现在考虑询问的左右移修改,其等价于对原数组的下标修改。我们只需要预处理出下标为 \(1, \cdots ,n\) 作为第一个下标的答案即可。直接枚举起点是不可行的,我们考虑每个数字在哪些数字作为起点时会做贡献。对于 \(a_i\) ,当移动后的下标为 \([a_i+1,n]\) 时会做贡献,此时起点的范围为 \([a_i+1-(i-1),n-(i-1)]\) 。注意,左端点可能为负数,要做循环处理,为了方便,所以将值域从 \([1,n]\) 变为 \([0,n-1]\)

对于反转,我们对反转数组也做一次上述预处理。在反转操作时,反转后新的起点坐标,相当于模意义下的旧坐标的相反数。

时间复杂度 \(O(n)\)

空间复杂度 \(O(n)\)

代码

#include <bits/stdc++.h>
using namespace std;
using ll = long long;

int main() {
    std::ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
    int n;
    cin >> n;
    vector<int> p(n);
    for (int i = 0;i < n;i++) cin >> p[i], p[i]--;

    auto f = [&]() {
        vector<int> ans(n + 1);
        for (int i = 0;i < n;i++) {
            int l = p[i] + 1 - i, r = n - 1 - i;
            if (l < 0) {
                ans[l + n]++;
                ans[n]--;
                ans[0]++;
                ans[r + 1]--;
            }
            else {
                ans[l]++;
                ans[r + 1]--;
            }
        }
        for (int i = 1;i < n;i++) ans[i] += ans[i - 1];
        return ans;
    };

    vector<vector<int>> ans(2);
    ans[0] = f();
    reverse(p.begin(), p.end());
    ans[1] = f();
    cout << ans[0][0] << '\n';

    int pos = 0;
    int flag = 0;

    int q;
    cin >> q;
    while (q--) {
        int t;
        cin >> t;
        if (t == 1) {
            int k;
            cin >> k;
            (pos -= k - n) %= n;
        }
        else if (t == 2) {
            int k;
            cin >> k;
            (pos += k) %= n;
        }
        else {
            pos = (n - pos) % n;
            flag ^= 1;
        }
        cout << ans[flag][pos] << '\n';
    }
    return 0;
}
posted @ 2023-06-24 17:23  空白菌  阅读(102)  评论(0编辑  收藏  举报