Codeforces Round 950 (Div. 3) A~F

A.Problem Generator(遍历)

题意:

弗拉德计划在下个月举行mmm轮比赛。每轮比赛应包含一个难度为"A"、“B”、“C”、“D”、“E”、"F"和"G"的问题。

弗拉德已经有了一个nnn个问题的问题库,其中第iii个问题的难度为aia_iai。这些问题可能不够多,所以他可能需要再想出一些问题。

弗拉德想要尽可能少地提出问题,所以他要求你找出他需要提出的问题的最小数量,以便举行mmm轮比赛。

例如,如果m=1m=1m=1n=10n=10n=10a=a=a='BGECDCBDEDBGECDCBDED",那么他需要提出两道难题:一道难度为"A",一道难度为"F"。

分析:

每个比赛都应该包含"A"、“B”、“C”、“D”、“E”、"F"和"G"的问题。遍历数据,暴力模拟统计需要加入的题目即可。

代码:

#include<bits/stdc++.h>

typedef long long LL;
using namespace std;
const LL N = 2e5 + 10;
int T, n, m;
char a[N];
map<char, int> k;

int main() {
    cin >> T;
    while (T--) {
        k.clear();
        cin >> n >> m;
        cin >> a + 1;
        for (int i = 1; i <= n; i++) {
            k[a[i]]++;
        }
        int cnt = 0;
        for (char i = 'A'; i <= 'G'; i++) {
            cnt += max(0, m - k[i]);
        }
        cout << cnt << endl;
    }
    return 0;
}

B.Choosing Cubes(思维)

题意:

德米特里有nnn个立方体,从左到右编号为111nnn。索引为fff的立方体是他的最爱。

德米特里把所有的立方体都扔到了桌子上,第iii个立方体显示了值aia_iai( 1≤ai≤1001\le a_i\le 1001ai100)。之后,他按照数值从大到小的非递增顺序排列这些立方体。如果两个立方体的数值相同,它们可以按照任意顺序排列。

排序后,德米特里取出了第一个kkk立方体。然后,他开始关注自己是否取出了最喜欢的立方体(注意,排序后立方体的位置可能会发生变化)。

例如,如果n=5n=5n=5f=2f=2f=2a=[4,3,3,2,3]a=[4,\color{green}3,3,2,3]a=[4,3,3,2,3](最喜欢的立方体用绿色标出)和k=2k=2k=2,可能会发生以下情况:

  • 在对a=[4,3,3,3,2]a=[4,\color{green}3,3,3,2]a=[4,3,3,3,2]排序后,由于最喜爱的立方体最终排在了第二位,因此它将被移除。
  • 在对a=[4,3,3,3,2]a=[4,3,\color{green}3,3,2]a=[4,3,3,3,2]排序后,由于最喜爱的魔方排在了第三位,因此它不会被移除。

分析:

我们只需要检查排完序之后比较在kkk位置的立方体aka_kak与最喜欢的立方体afa_faf的大小即可,如果ak>afa_k\gt a_fak>af,说明afa_faf的位置在kkk之前,一定会被删;如果ak<afa_k\lt a_fak<af,说明afa_faf的位置在kkk之后,一定不会被删;如果ak=afa_k=a_fak=af,检查ak+1a_{k+1}ak+1,如果ak+1=afa_{k+1}=a_fak+1=af,则有可能被删,否则一定被删。

代码:

#include<bits/stdc++.h>

using namespace std;

void solve() {
    int n, f, k;
    cin >> n >> f >> k;
    vector<int> a(n + 1);
    for (int i = 1; i <= n; i++) {
        cin >> a[i];
    }
    int fa = a[f];
    sort(a.begin() + 1, a.end(), greater<int>());
    if (k == n)
        cout << "YES" << endl;
    else if (fa > a[k])
        cout << "YES" << endl;
    else if (fa < a[k])
        cout << "NO" << endl;
    else {
        if (a[k + 1] == fa)
            cout << "MAYBE" << endl;
        else
            cout << "YES" << endl;
    }
}

int main() {
    int t;
    cin >> t;
    while (t--) {
        solve();
    }
    return 0;
}

C.Sofia and the Lost Operations(思维)

题意:

索菲亚有一个由nnn个整数a1,a2,…,ana_1,a_2,\ldots,a_na1,a2,,an组成的数组。有一天,她对这个数组感到厌倦,于是决定依次对它进行mmm次修改操作。

每个修改操作都由一对数字⟨cj,dj⟩\langle c_j,d_j\ranglecj,dj来描述,这意味着数组中索引为cjc_jcj的元素应赋值为djd_jdj,即执行赋值acj=dja_{c_j}=d_jacj=dj依次执行所有修改操作后,索菲亚丢弃了得到的数组。

最近,你发现了一个由nnn个整数组成的数组b1,b2,…,bnb_1,b_2,\ldots,b_nb1,b2,,bn。你想知道这个数组是否是索菲亚的数组。你知道原始数组的值以及d1,d2,…,dmd_1,d_2,\ldots,d_md1,d2,,dm的值。结果发现数值c1,c2,…,cmc_1,c_2,\ldots,c_mc1,c2,,cm丢失了。

是否存在一个序列c1,c2,…,cmc_1,c_2,\ldots,c_mc1,c2,,cm使得对数组a1,a2,…,ana_1,a_2,\ldots,a_na1,a2,,an的修改操作⟨c1,d1,⟩,⟨c2,d2,⟩,…,⟨cm,dm⟩\langle c_1,d_1,\rangle,\langle c_2,d_2,\rangle,\ldots,\langle c_m,d_m\ranglec1,d1,,c2,d2,,,cm,dm顺序应用将其转换为数组b1,b2,…,bnb_1,b_2,\ldots,b_nb1,b2,,bn

分析:

若最后一个修改数存在于bbb数组中,那么只需要看这些修改的数能否把aaa数组中待修改的数全部覆盖即可,否则一定不存在。

所以判断最后一个是否合法,前面的能否一一匹配即可。

代码:

#include<bits/stdc++.h>

using namespace std;
const int N = 2e5 + 10;
const int MOD = 998244353;
int n, a[N], m, b[N], c[N];
map<int, int> apper, be;

void solve() {
    apper.clear();
    be.clear();
    cin >> n;
    for (int i = 1; i <= n; i++) {
        cin >> a[i];
    }
    for (int i = 1; i <= n; i++) {
        cin >> b[i];
        apper[b[i]]++;
    }
    cin >> m;
    for (int i = 1; i <= m; i++) {
        cin >> c[i];
        be[c[i]]++;
    }
    if (apper.find(c[m]) == apper.end())
        cout << "no" << endl;
    else {
        bool flag = 1;
        for (int i = 1; i <= n; i++) {
            if (a[i] != b[i]) {
                if (!be[b[i]]) {
                    flag = 0;
                    break;
                }
                be[b[i]]--;
            }
        }
        if (flag)
            cout << "yes" << endl;
        else
            cout << "no" << endl;
    }
}

int main() {
    int t;
    cin >> t;
    while (t--) {
        solve();
    }
    return 0;
}

D.GCD-sequence(模拟)

题意:

两个整数xxxyyy的GCD(最大公约数)是zzz可以整除xxxyyy的最大整数。例如,GCD(36,48)=12GCD(36,48)=12GCD(36,48)=12GCD(5,10)=5GCD(5,10)=5GCD(5,10)=5GCD(7,11)=1GCD(7,11)=1GCD(7,11)=1

克里斯蒂娜有一个数组aaa,其中正好包含nnn个正整数。她想通过计算每一对相邻数字的GCD得到一个新数组bbb,称为GCD序列。

因此,GCD序列bbb中的元素将使用公式bi=GCD(ai,ai+1)b_i=GCD(a_i,a_{i+1})bi=GCD(ai,ai+1)计算1≤i≤n−11\le i\le n-11in1

确定是否有可能从数组aaa恰好删除一个数,从而使GCD序列bbb不递减(即bi≤bi+1b_i\le b_{i+1}bibi+1始终为真)。

例如,假设克里斯蒂娜有一个数组aaa=[20,6,12,3,48,3620,6,12,3,48,3620,6,12,3,48,36]。如果她从中取出a4=3a_4=3a4=3并计算bbb的GCD序列,她会得到:

  • b1=GCD(20,6)=2b_1=GCD(20,6)=2b1=GCD(20,6)=2
  • b2=GCD(6,12)=6b_2=GCD(6,12)=6b2=GCD(6,12)=6
  • b3=GCD(12,48)=12b_3=GCD(12,48)=12b3=GCD(12,48)=12
  • b4=GCD(48,36)=12b_4=GCD(48,36)=12b4=GCD(48,36)=12

得到的GCD序列bbb=[2,6,12,122,6,12,122,6,12,12]是非递减的,因为b1≤b2≤b3≤b4b_1\le b_2\le b_3\le b_4b1b2b3b4

分析:

读题发现是大模拟,判断前驱合法,后继合法,拼接即可。

代码:

#include<bits/stdc++.h>

typedef long long LL;
using namespace std;
const int N = 2e5 + 10;
const LL MAXN = 0x3f3f3f3f;
int n, a[N];
int gcd1[N];
bool pre[N], suc[N];
map<int, int> apper, be;

LL _gcd(LL a, LL b) {
    return b > 0 ? _gcd(b, a % b) : a;
}

void solve() {
    apper.clear();
    be.clear();
    cin >> n;
    for (int i = 1; i <= n; i++) {
        cin >> a[i];
        if (i > 1)
            gcd1[i] = _gcd(a[i], a[i - 1]);
    }
    gcd1[0] = gcd1[1] = 0;
    gcd1[n + 1] = MAXN;
    pre[1] = pre[2] = true;
    for (int i = 3; i <= n; i++) {
        pre[i] = pre[i - 1] && (gcd1[i] >= gcd1[i - 1]);
    }
    suc[n] = suc[n - 1] = true;
    for (int i = n - 2; i >= 1; i--) {
        suc[i] = suc[i + 1] && (gcd1[i + 1] <= gcd1[i + 2]);
    }
    bool flag = 0;
    for (int i = 2; i < n; i++) {
        int g = (__gcd(a[i - 1], a[i + 1]));
        if (pre[i - 1] && suc[i + 1] && g >= gcd1[i - 1] && g <= gcd1[i + 2]) {
            flag = 1;
            break;
        }
    }
    if (flag || pre[n - 1] || suc[2])
        cout << "Yes" << endl;
    else
        cout << "No" << endl;
}

int main() {
    int t;
    cin >> t;
    while (t--) {
        solve();
    }
    return 0;
}

E.Permutation of Rows and Columns(遍历)

题意:

给你一个大小为nnnmmm的矩阵aaa,其中包含从111n⋅mn\cdot mnm的整数排列。

一个由nnn个整数组成的排列是一个数组,其中包含了从111nnn的所有数字,而这些数字恰好出现过一次。例如,数组[1][1][1][2,1,3][2,1,3][2,1,3][5,4,3,2,1][5,4,3,2,1][5,4,3,2,1]是排列,而数组[1,1][1,1][1,1][100][100][100][1,2,4,5][1,2,4,5][1,2,4,5]不是排列。

如果一个矩阵的所有元素都写出后,得到的数组是一个排列,那么这个矩阵就包含一个排列。矩阵[[1,2],[3,4]][[1,2],[3,4]][[1,2],[3,4]],[[1]][[1]][[1]],[[1,5,3],[2,6,4]][[1,5,3],[2,6,4]][[1,5,3],[2,6,4]]包含排列,而矩阵[[2]][[2]][[2]],[[1,1],[2,2]][[1,1],[2,2]][[1,1],[2,2]],[[1,2],[100,200]][[1,2],[100,200]][[1,2],[100,200]]不包含排列。

您可以在一次操作中执行以下两个操作之一:

  • 选择列cccddd1≤c,d≤m1\le c,d\le m1c,dm,c≠dc\ne dc=d)并交换这些列;
  • 选择行cccddd(1≤c,d≤n1\le c,d\le n1c,dn,c≠dc\ne dc=d),并交换这些行。

您可以执行任意数量的操作。

给你原始矩阵aaa和矩阵bbb。您的任务是确定是否可以通过给定的运算将矩阵aaa变换为矩阵bbb

分析:

很容易就可以观察到,无论怎么进行交换,每一行拥有的数字都不会变,只是位置改变了,对于每一列同样也是如此。只需要逐行和逐列的去检查bbb矩阵的一行或一列对应的数字集合是否在aaa中出现即可。

代码:

#include<bits/stdc++.h>

using namespace std;
const int MOD = 998244353;

void solve() {
    int n, m;
    cin >> n >> m;
    vector<vector<int>> a(n, vector<int>(m));
    vector<vector<int>> b(n, vector<int>(m));
    map<vector<int>, int> mp_col;
    for (int i = 0; i < n; i++) {
        vector<int> temp;
        for (int j = 0; j < m; j++) {
            cin >> a[i][j];
            temp.emplace_back(a[i][j]);
        }
        sort(temp.begin(), temp.end());
        mp_col[temp]++;
    }
    for (int i = 0; i < n; i++) {
        for (int j = 0; j < m; j++) {
            cin >> b[i][j];
        }
    }
    for (int i = 0; i < n; i++) {

        vector<int> temp;
        for (int j = 0; j < m; j++) {
            temp.emplace_back(b[i][j]);
        }
        sort(temp.begin(), temp.end());
        if (mp_col[temp] == 0) {
            cout << "NO" << endl;
            return;
        }
    }
    map<vector<int>, int> mp_row;
    for (int j = 0; j < m; j++) {
        vector<int> temp;
        for (int i = 0; i < n; i++) {
            temp.emplace_back(a[i][j]);
        }
        sort(temp.begin(), temp.end());
        mp_row[temp]++;
    }
    for (int j = 0; j < m; j++) {
        vector<int> temp;
        for (int i = 0; i < n; i++) {
            temp.emplace_back(b[i][j]);
        }
        sort(temp.begin(), temp.end());
        if (mp_row[temp] == 0) {
            cout << "NO" << endl;
            return;
        }
    }
    cout << "YES" << endl;
}

int main() {
    int t;
    cin >> t;
    while (t--) {
        solve();
    }
    return 0;
}

F1.Field Division (easy version)(思维)

题意:

这是问题的简易版;它与困难版的区别仅在于问题。简易版只需输出某些值是否为非零。而困难版则需要输出确切的数值。

爱丽丝和鲍勃正在分割田地。田地是一个大小为n×mn \times mn×m(2≤n,m≤1092\le n,m\le 10^92n,m109) 的矩形,行的编号从上到下为111nnn,列的编号从左到右为111mmm。位于行rrr和列ccc交点的单元格表示为(r,cr,cr,c)。

鲍勃有kkk(2≤k≤2⋅1052\le k\le 2\cdot 10^52k2105)个喷泉,它们都位于字段的不同单元格中。爱丽丝负责分割田块,但她必须满足几个条件:

  • 要分割田块,爱丽丝要从田块左侧或上侧的任意空闲(无喷泉)格子开始移动,每次移动都要向下或向移动到相邻的格子。她的路径将在田地的右侧或底侧结束。
  • 爱丽丝的路径会将田地分成两部分–一部分属于爱丽丝(这部分包括她路径上的单元格),另一部分属于鲍勃。
  • 爱丽丝将拥有包含单元格(n,1n,1n,1)的部分。
  • 鲍勃将拥有包括单元格(1,m1,m1,m)的部分。

爱丽丝希望以这样的方式划分区域,以获得尽可能多的单元格。

鲍勃希望保留所有喷泉的所有权,但他可以将其中一个喷泉交给爱丽丝。首先,输出整数α\alphaα-如果鲍勃不给爱丽丝任何喷泉(即所有喷泉都保留在鲍勃的地块上),爱丽丝地块的最大可能面积。然后输出kkk个非负整数a1,a2,…,aka_1,a_2,\dots,a_ka1,a2,,ak,其中:

  • ai=0a_i=0ai=0,如果鲍勃给了爱丽丝iii个喷泉后,爱丽丝的地块的最大可能大小没有增加(即仍然等于α\alphaα);
  • ai=1a_i=1ai=1,如果鲍勃给了爱丽丝iii个喷泉后,爱丽丝的地块的最大可能大小增加了(即大于α\alphaα)。

分析:

手推一下可以发现这个平面面积的最大是:从左到右喷泉的最低面跟平面底部的差值的总和,然后如果某个喷泉会使得这个最低面增大,那么去掉这个喷泉就可以使最终答案增大,那么可以用mapmapmap存下二维中第ccc列中的最大值rrr,从左到右维护一个当前最大值,计算当前的面积总和,如果某一列使得这个最大值扩大了,那么就标记这一列,最后只需要判断这个喷泉是不是所在列的最大值以及所在列是不是被标记即可。

代码:

#include<bits/stdc++.h>

typedef long long LL;
using namespace std;
const int N = 2e5 + 10;
const int MOD = 998244353;
int ar[N], ac[N];

void solve() {
    int n, m, k;
    cin >> n >> m >> k;
    map<int, int> mp;
    map<int, bool> fc;
    int minn = 0;
    for (int i = 0; i < k; i++) {
        cin >> ar[i] >> ac[i];
        minn = max(minn, ac[i]);
        mp[ac[i]] = max(ar[i], mp[ac[i]]);
    }
    LL res = 0;
    int maxx = 0, le = 1;
    for (auto [c, r]: mp) {
        res += (LL) (c - le) * (n - maxx);
        if (r > maxx) {
            fc[c] = 1;
            maxx = r;
        }
        le = c;
    }
    res += (LL) (m - le + 1) * (n - maxx);
    cout << res << endl;
    for (int i = 0; i < k; i++) {
        if (fc[ac[i]] && ar[i] == mp[ac[i]])
            cout << 1 << " ";
        else
            cout << 0 << " ";
    }
    cout << endl;
}

int main() {
    int t;
    cin >> t;
    while (t--) {
        solve();
    }
    return 0;
}

F2.Field Division (hard version)(数学)

题意:

这是问题的困难版本;它与简单版本的区别仅在于问题。简易版只需输出某些值是否为非零。而困难版则需要输出确切的数值。

爱丽丝和鲍勃正在分割田地。田地是一个大小为n×mn\times mn×m(2≤n,m≤1092\le n,m\le 10^92n,m109)的矩形;行的编号从上到下为111nnn,列的编号从左到右为111mmm。位于第rrr行和第ccc列交叉点的单元格表示为(r,cr,cr,c)。

鲍勃有kkk(2≤k≤2⋅1052\le k\le 2\cdot 10^52k2105)个喷泉,它们都位于字段的不同单元格中。爱丽丝负责分割田块,但她必须满足几个条件:

  • 要分割田块,爱丽丝要从田块左侧或上侧的任意空闲(无喷泉)格子开始移动,每次移动都要向下或向移动到相邻的格子。她的路径将在田地的右侧或底侧结束。
  • 爱丽丝的路径会将田地分成两部分–一部分属于爱丽丝(这部分包括她路径上的单元格),另一部分属于鲍勃。
  • 爱丽丝将拥有包含单元格(n,1n,1n,1)的部分。
  • 鲍勃将拥有包括单元格(1,m1,m1,m)的部分。

爱丽丝希望以这样的方式划分区域,以获得尽可能多的单元格。

鲍勃希望保留所有喷泉的所有权,但他可以将其中一个喷泉交给爱丽丝。首先,输出整数α\alphaα-如果鲍勃不给爱丽丝任何喷泉(即所有喷泉都保留在鲍勃的地块上),爱丽丝地块的最大可能面积。

然后输出kkk个非负整数a1,a2,…,aka_1,a_2,\dots,a_ka1,a2,,ak,其中aia_iai是这样一个值:当鲍勃给了爱丽丝iii个喷泉后,她的地块的最大面积将是α+ai\alpha+a_iα+ai

分析:

仍然可以使用简单版的思路来预先计算所存储的前缀信息。

爱丽丝的地块面积只有在移除第三种喷泉位置时才会发生变化,我们将其称为角落。

只有当喷泉被移除时,它才会停止成为一个角落,因为前缀上最左边的角落在移除后无法再向左移动。

为了计算面积的变化,我们要注意一个重要的问题:如果喷泉jjj不是角落,那么它要么不能成为角落,要么只有在移除排序在它之前的最后一个角落后才能成为角落。它是喷泉jjj前面最左边的一个角落,严格来说,排序后的下一个角落会更高。

因此,我们需要做的工作如下:计算每个角落的面积,在计算下一个角落之前不考虑它,这将得益于我们在计算面积时进行的预计算。每个喷泉只需处理一次,因此时间复杂度为O(n)O(n)O(n)

代码:

#include <bits/stdc++.h>

typedef long long LL;
#define x first
#define y second
#define all(a) a.begin(), a.end()
using namespace std;

bool cmp(pair<LL, LL> &a, pair<LL, LL> &b) {
    if (a.x != b.x) return a.x > b.x;
    return a.y < b.y;
}

void solve(LL tc) {
    LL n, m, k;
    cin >> n >> m >> k;
    vector<pair<LL, LL>> a(k);
    map<pair<LL, LL>, LL> idx;
    for (LL i = 0; i < k; ++i) {
        cin >> a[i].x >> a[i].y;
        idx[a[i]] = i;
    }
    idx[{0, 0}] = k++;
    a.emplace_back(0, 0);
    sort(all(a), cmp);
    vector<LL> ans(k);
    vector<LL> total(k + 1), cur(k + 1, m + 1), last(k + 1, n);
    for (LL i = 1; i <= k; ++i) {
        auto e = a[i - 1];
        total[i] = total[i - 1];
        cur[i] = cur[i - 1];
        last[i] = last[i - 1];
        if (cur[i] > e.y) {
            ans[idx[e]] = 1;
            total[i] += (cur[i] - 1) * (last[i] - e.x);
            cur[i] = e.y;
            last[i] = e.x;
        }
    }
    cout << total[k] << "\n";
    for (LL i = 1; i <= k; ++i) {
        auto e = a[i - 1];
        if (ans[idx[e]] == 0)continue;
        LL tot = total[i - 1];
        LL cr = cur[i - 1];
        LL lst = last[i - 1];
        for (LL j = i + 1; j <= k; ++j) {
            auto ee = a[j - 1];
            if (cr > ee.y) {
                tot += (cr - 1) * (lst - ee.x);
                cr = ee.y;
                lst = ee.x;
            }
            if (ans[idx[ee]] == 1) {
                ans[idx[e]] = tot - total[j];
                break;
            }
        }
    }
    ans.pop_back();
    for (LL e: ans) cout << e << " ";
}

int main() {
    LL t;
    cin >> t;
    for (LL i = 1; i <= t; ++i) {
        solve(i);
        cout << endl;
    }
    return 0;
}

赛后交流

在比赛结束后,会在交流群中给出比赛题解,同学们可以在赛后查看题解进行补题。

群号: 704572101,赛后大家可以一起交流做题思路,分享做题技巧,欢迎大家的加入。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值