Contestant. Rank 1936. Rating +9.

A. Serval and Mocha's Array

题意

给定一个数组 \(a\),将其重新排序,满足对于所有前缀,如前 \(i\) 个数,满足它们的 \(gcd \leq i\)

思路

首先,最优的方法当然是互质,只要把互质的两个数放到第一个,那么后面的 \(gcd\) 全都是 \(1\) 了。

其次,前两个数可以有公约数 \(2\),这是毋庸置疑的。但是,若我们继续下去,前 \(3\) 个数可以有公约数 \(3\),...,前 \(6\) 个数可以有公约数 \(6\)。停,不对劲:既然有公约数 \(2,3\),那么一定能被 \(6\) 整除,那么前两个数的 \(gcd\) 一定至少是 \(6\) 了,产生了矛盾。

所以,我们只需找出一对数,满足 \(gcd \leq 2\) 即可。

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

对应AC代码

#include <bits/stdc++.h>

using namespace std;

#define int long long
#define pii pair<int, int>

const int N = 2e5 + 10, inf = 0x3f3f3f3f;

int a[N];

int gcd(int x, int y) {
    while (y != 0) {
        int tmp = x;
        x = y;
        y = tmp % y;
    }
    return x;
}

signed main() {
    ios::sync_with_stdio(false), cin.tie(nullptr), cout.tie(nullptr);
    int t;
    cin >> t;
    while(t --){
        int n;
        cin >> n;
        for(int i=0;i<n;i++) cin >> a[i];
        bool f = false;
        for(int i=0;i<n;i++)
            for(int j=i;j<n;j++){
                if(gcd(a[i], a[j]) <= 2) {
                    f = true;
                    break;
                }
            }
        cout << (f ? "YES\n" : "NO\n");
    }
}

为什么赛后反而思路这么清晰了((

B. Serval and Inversion Magic

题意

给定一个二进制字符串,任选一个区间,将区间内的所有数取反,输出是否可以将整个字符串变为回文字符串。

思路

考虑到回文串的对称性,我们只需修改 \([0, \frac{n}{2}]\) 内的数即可。

下面是一种模拟思路:

我们不妨从外向里遍历,比较 \(i\)\(n - i - 1\) 对应的数,若出现了不同的数,记录当前下标开始需要取反,然后一直找到结束,或者出现相同的数。出现相同数后,我们标记一下已经进行过取反操作,当我们再次遇到不同数的时候,考虑到只能选一个区间,所以直接返回 \(NO\)

否则,一定是有解的。

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

对应AC代码

#include <bits/stdc++.h>

using namespace std;

#define int long long
#define pii pair<int, int>

const int N = 2e5 + 10, inf = 0x3f3f3f3f;

signed main() {
    ios::sync_with_stdio(false), cin.tie(nullptr), cout.tie(nullptr);
    int t;
    cin >> t;
    while(t --){
        int n;
        cin >> n;
        string a;
        cin >> a;
        bool f = true, have = false, now = false;
        for(int i=0;i<n/2;i++){
            if(a[i] == a[n - i - 1]){
                if(now){
                    now = false;
                    have = true;
                }
            }else{
                if(have){
                    f = false;
                    break;
                }
                now = true;
            }
        }
        cout << (f ? "YES\n" : "NO\n");
    }
}

我的评价是:比A题简单

C. Serval and Toxel's Arrays

题意

给定一个无重复元素的数组 \(a\),对于 \(q\) 个操作给定的 \(p_i, v_i\),将 \(a_{p_i}\) 改为 \(v_i\)。每次操作后,将会得到一个新数组,新数组满足无重复元素。每次操作的对象为上次操作后的数组。对于所有数组(原数组 + 所有新数组),输出所有任意两个数组拼接后不同数字的个数的和。

思路

显然,我们不可能去暴力计算,而相反地,我们可以将问题拆分为所有单个数字的贡献和。

具体地说,对于一个数字,若它自始至终未被修改,那么任意两个数组组合,都会留下它,那么它的贡献即为 \(C_{m + 1}^2\)

若它被修改了,那么在它未出现的时候,它将毫无贡献。因此,我们需要减去这些无贡献的数组组合,也就是 \(C_{m - cnt + 1}^2\)。因而,得到该条件下的总贡献:\(C_{m + 1}^2 - C_{m - cnt + 1}^2\)

当然,我们还需算出一个数的出现次数 \(cnt\),考虑到无重复,所以我们只需模拟即可。

时间复杂度:\(O(m+n)\)

对应AC代码

#include <bits/stdc++.h>

using namespace std;

#define int long long
#define pii pair<int, int>

const int N = 4e5 + 10, inf = 0x3f3f3f3f;

int cnt[N];
pii a[N];

signed main() {
    ios::sync_with_stdio(false), cin.tie(nullptr), cout.tie(nullptr);
    int t;
    cin >> t;
    while(t --){
        int n, m;
        cin >> n >> m;
        for(int i=1;i<=n+m;i++) cnt[i] = 0;
        for(int i=1;i<=n;i++) {
            cin >> a[i].first;
            a[i].second = 0;
        }
        for(int i=1;i<=m;i++){
            int p, v;
            cin >> p >> v;
            cnt[a[p].first] += i - a[p].second;
            a[p] = {v, i};
        }
        for(int i=1;i<=n;i++) cnt[a[i].first] += m + 1 - a[i].second;
        int ans = 0;
        for(int i=1;i<=n+m;i++){
            if(cnt[i] == 0) continue;
            if(cnt[i] > m) ans += m * (m + 1) / 2;
            else ans += (m * (m + 1) - (m - cnt[i]) * (m - cnt[i] + 1)) / 2;
        }
        cout << ans << '\n';
    }
}

我蠢了