Codeforces Round 917 (Div. 2) A~F

博客围绕Codeforces的多道题目展开,涵盖A - F题。涉及思维、枚举、树状数组、构造等算法。如A题通过分析数组元素情况确定使乘积最小的操作次数;C题枚举操作方式求数组得分最大值;D题用树状数组计算数组逆序对数量等,并给出各题代码。

A.Least Product(思维)

题意:

给出一个数组a1,a2,...,ana_1, a_2, ..., a_na1,a2,...,an,你可以进行若干次以下操作:

  • 选择数组中的一个元素aia_iai,将这个数字修改为0∼ai0 \sim a_i0ai之间的任意数字。

问,最少需要多少次操作可以使得∏i=1nai\prod\limits_{i = 1}^{n}a_ii=1nai的结果最小,并输出对应的操作。

分析:

可以将问题分成两种情况:

  • 存在ai=0a_i = 0ai=0或负数的出现次数为奇数,此时无论进行任何操作都无法使结果变得更小,输出0

  • 其他情况,由于数字只能被修改到0∼ai0 \sim a_i0ai之间,那么正负数是无法互相转换的,因此,当结果为正数时,任选一个aia_iai,将其修改为000,就能获得最小的结果

代码:

#include<bits/stdc++.h>

using namespace std;
typedef long long LL;
const int MAXN = 3e5 + 5e2;


void solve() {
    int n;
    cin >> n;
    int zero = 0, sign = 0;
    for (int i = 1; i <= n; i++) {
        int a;
        cin >> a;
        if (a == 0) {
            zero = 1;
        } else if (a < 0) {
            sign++;
        }
    }
    if (zero || sign % 2 == 1) cout << 0 << endl;
    else {
        cout << 1 << endl;
        cout << "1 0" << endl;
    }
}

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

B.Erase First or Second Letter(思维)

题意:

给出一个长度为nnn的字符串sss,你可以进行若干次以下操作:

  • 移除第一个字符

  • 移除第二个字符

问:经过若干次操作后,可以获得多少种不同的非空字符串?

分析:

将字符串划分为前后两部分,枚举两部分的分界点,每次枚举完分界点后,将后半部分视为不进行修改的部分,仅会对前半部分进行操作,那么由于每次枚举的后半部分均不同,为了保证方案的独立性,前半部分仅保留一个字符,那么此时的方案数就是前半部分中包含的不同字母的数量。

使用计数数组或set动态记录不同字符数量,每次枚举分界点后更新前半部分字符种类并记录到答案中即可。

代码:

#include<bits/stdc++.h>

using namespace std;
typedef long long LL;
const int MAXN = 3e5 + 5e2;

int vis[30];

void solve() {
    memset(vis, 0, sizeof (vis));
    int n;
    string s;
    cin >> n >> s;
    int cnt = 0, ans = 0;
    for (int i = 0; i < n; i++) {
        vis[s[i] - 'a']++;
        if (vis[s[i] - 'a'] == 1) cnt++;
        ans += cnt;
    }
    cout << ans << endl;
}

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

C.Watering an Array(枚举)

题意:

给出一个长度为nnn的数组aaa,你将在接下来的ddd天里每天选择执行以下两个操作之一:

  • 给所有的a1,a2,...,abia_1, a_2, ..., a_{b_i}a1,a2,...,abi加上一,其中bib_ibi为题目给出的第iii天的操作数。

  • 数组aaaai=ia_i = iai=i的数量就是你本轮获得的得分,然后将aaa数组所有元素修改为000

问:最多可以获得多少得分?

Tips:由于天数较多,因此bbb数组采用b=[v1,v2,...,vk,v1,v2,...,vk,...]b = [v_1, v_2, ..., v_k, v_1, v_2, ..., v_k, ...]b=[v1,v2,...,vk,v1,v2,...,vk,...]的形式给出。

分析:

如果数组aaa开始全部为000,那么最优策略就是操作1,21,21,2轮流进行,可以获得⌊d2⌋\lfloor\frac{d}{2}\rfloor2d点得分(注意:开始时若a1≥1a_1 \ge 1a11,则需要使用一次操作222a1a_1a1修改为000,此时得分为(⌊d−12⌋+开始时数组元素可以产生的得分(\lfloor\frac{d - 1}{2}\rfloor + \text{开始时数组元素可以产生的得分}(⌊2d1+开始时数组元素可以产生的得分)。

而由于初始的数组是包含初始数字的,且长度为nnn的数组最多可以产生nnn点得分,因此,只需要在2×n−12 \times n - 12×n1的操作次数内均有可能超过循环操作获取的nnn点固定得分。

枚举第一次进行操作222前会进行多少次操作111,每次遍历数组统计得分,剩余的操作次数使用最优策略进行。记录过程中最大的得分即可。

坑点

既然获取的长度为nnn的数字最高可以产生nnn点得分,那么只枚举nnn次操作是否就够了?

反例:

原数组为2,1,2,...,n−12, 1, 2, ..., n - 12,1,2,...,n1,给出的bbb数组为[1,1,...,1,n][1, 1, ..., 1, n][1,1,...,1,n],其中bi=nb_i = nbi=n前有(2×n)−4(2 \times n) - 4(2×n)4个1,此时d=2×nd = 2 \times nd=2×n

可以发现,枚举0∼n0 \sim n0n次操作111,均无法产生额外得分,此时获得的最高总分为⌊d−12⌋=n−1\lfloor \frac{d - 1}{2} \rfloor = n - 12d1=n1,而依次使用(2×n)−3(2 \times n) - 3(2×n)3次操作111,再进行操作222,可以获得n−1n - 1n1点得分,此时还剩下两次操作,还可以继续一次最优策略,最后得分为nnn

代码:

#include<bits/stdc++.h>

using namespace std;
typedef long long LL;
const int MAXN = 3e5 + 5e2;

LL a[MAXN], b[MAXN];

void solve() {
    LL n, k, d;
    cin >> n >> k >> d;
    LL ans = 0;
    for (int i = 1; i <= n; i++) cin >> a[i];
    for (int i = 0; i < k; i++) cin >> b[i];
    for (int i = 0; i <= 2 * n && i < d; i++) {
        LL cnt = 0;
        for (int j = 1; j <= n; j++) if (a[j] == j) cnt++;
        ans = max(ans, cnt + (d - i - 1) / 2);
        for (int j = 1; j <= b[i % k]; j++) {
            if (a[j] == j) cnt--;
            a[j]++;
            if (a[j] == j) cnt++;
        }
    }
    cout << ans << endl;
}

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

D.Yet Another Inversions Problem(树状数组+逆序对)

题意:

给定一个由1112n−12n-12n1的奇数组成的排列p0,p1,…,pn−1p_0,p_1,…,p_{n−1}p0,p1,,pn1和一个由000k−1k-1k1的整数组成的排列q0,q1,…,qk−1q_0,q_1,…,q_{k−1}q0,q1,,qk1。定义长度为nknknk的数组a0,a1,…,ank−1a_0,a_1,…,a_{nk−1}a0,a1,,ank1如下:对于所有0≤i<n0 \le i \lt n0i<n和所有0≤j<k0 \le j \lt k0j<k,有ai⋅k+j=pi⋅2qja_{i⋅k+j}=p_i⋅2^{q_j}aik+j=pi2qj。例如,如果p=[3,5,1]p=[3,5,1]p=[3,5,1]q=[0,1]q=[0,1]q=[0,1],那么a=[3,6,5,10,1,2]a=[3,6,5,10,1,2]a=[3,6,5,10,1,2]。所有数组都是从零开始索引的。注意,数组aaa中每个元素是唯一确定的。计算数组aaa中逆序对(i,j)(0≤i<j<nk)(i,j)(0 \le i < j < nk)(i,j)(0i<j<nk)的数量,结果对998244353998244353998244353取模。

分析:

对于同一个aia_iai,逆序对个数仅取决于q{q}q的逆序对个数。

考虑用树状数组求逆序对个数的过程。对该过程稍作修改,可以得到下面的算法:
对于aia_iai,大小在(ai,2ai)(a_i,2a_i)(ai,2ai)之间的数而言,ai⋅2pja_i⋅2^{p_j}ai2pj与这些数能够形成的逆序对为(ai⋅2pj,ax⋅2pj−1)(a_i⋅2^{p_j},a_x⋅2^{p_j-1})(ai2pj,ax2pj1),(ai⋅2pj,ax⋅2pj−2)(a_i⋅2^{p_j},a_x⋅2^{p_j-2})(ai2pj,ax2pj2)……(ai⋅2pj,ax⋅20)(a_i⋅2^{p_j},a_x⋅2^0)(ai2pj,ax20),共有pjp_jpj个,aia_iai与这些数所能形成的逆序对数量为∑i=0kpi=(0+k−1)×k2\sum\limits_{i=0}^{k}p_i=(0+k-1)\times\frac{k}{2}i=0kpi=(0+k1)×2k。同理对大小在(2ai,4ai)(2a_i,4a_i)(2ai,4ai)的数而言,aia_iai与这些数所能形成的逆序对数量为∑i=0kpi=(0+k−2)×k−12\sum\limits_{i=0}^{k}p_i=(0+k-2)\times\frac{k-1}{2}i=0kpi=(0+k2)×2k1,如此不断向上倍增求答案,直到上限。

相反的,对于大小在(ai2,ai)(\frac{a_i}{2},a_i)(2ai,ai)之间的数,可以按相同的方式考虑,如此不断向下除二求答案,直到下限。

代码:

#include <bits/stdc++.h>

using namespace std;
typedef long long LL;
const LL mod = 998244353;
const LL N = 5e5;

struct BIT {
    LL n;
    vector<LL> tr;

    BIT(LL n) : n(n), tr(n + 1, 0) {
    }

    LL lowbit(LL x) {
        return x & -x;
    }

    void modify(LL x, LL m) {
        for (LL i = x; i <= n; i += lowbit(i)) {
            tr[i] += m;
        }
    }

    void modify(LL l, LL r, LL m) {
        modify(l, m);
        modify(r + 1, -m);
    }

    LL query(LL x) {
        LL res = 0;
        for (LL i = x; i; i -= lowbit(i))
            res += tr[i];
        return res;
    }

    LL query(LL x, LL y) {
        return query(y) - query(x);
    }
};

LL tmp[N];

LL merge_sort(LL q[], LL l, LL r) {
    if (l >= r)
        return 0;

    LL mid = (l + r) >> 1;
    LL res = merge_sort(q, l, mid) + merge_sort(q, mid + 1, r);
    LL i = l, j = mid + 1, k = 0;
    while (i <= mid && j <= r) {
        if (q[i] <= q[j]) tmp[k++] = q[i++];
        else {
            res += mid - i + 1;
            tmp[k++] = q[j++];
        }
    }

    while (i <= mid)
        tmp[k++] = q[i++];
    while (j <= r)
        tmp[k++] = q[j++];

    for (LL i = l, j = 0; i <= r; i++, j++)
        q[i] = tmp[j];

    return res;
}

void solve() {
    LL n, k;
    cin >> n >> k;
    BIT num(4 * n + 5), sum(k + 5);
    LL a[n], b[k];
    for (LL i = 0; i < n; i++) {
        cin >> a[i];
        num.modify(a[i], 1);
    }
    for (LL i = 0; i < k; i++) {
        cin >> b[i];
    }
    LL ans = merge_sort(b, 0, k - 1);
    ans *= n;
    ans %= mod;
    for (LL i = 0; i < n; i++) {
        LL x = a[i];
        LL cnt = 1;
        while (x < 2 * n) {
            LL t = x * 2;
            LL nn = num.query(x, t);
            if (nn == 0) {
                x = t;
                cnt++;
                continue;
            }
            LL end = max(1LL * 0, k - cnt);
            LL ns = (1 + end) * end / 2;
            ans += ns * nn;
            ans %= mod;
            x = t;
            cnt++;
        }
        x = a[i];
        cnt = 1;
        while (1) {
            if (x == 1)
                break;
            LL t = (x + 1) / 2;
            LL nn = num.query(t - 1, x - 1);
            if (nn == 0) {
                x = t;
                cnt++;
                continue;
            }
            LL st = min(k, cnt);
            LL cntt = k - st + 1;
            LL res = k - cntt;
            LL ns = k * res + (st + k) * cntt / 2;
            ans += nn * ns;
            ans %= mod;;
            x = t;
            cnt++;
        }
        num.modify(a[i], -1);
    }
    cout << ans << endl;
}

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

E.Construct Matrix(构造)

题意:

给定一个偶数nnn和一个整数kkk。构造一个大小为n×nn×nn×n的矩阵,由数字000111组成,使其满足以下条件,或者输出不可能

所需满足的条件:

  • 矩阵中所有数的和正好是kkk;
  • iii行中所有数字按位异或对每个iii都是相同的;
  • jjj列中所有数字按位异或对每个jjj都是相同的。

分析:

首先,因为所有列的XORXORXORXORXORXOR显然为000(因为nnn为偶),因此所有数的XORXORXOR也为000,故kkk不可能为奇数。

如果kkk444的倍数,这种情况是比较简单的。易知一个2x22x22x2的全111矩阵可以在不破坏原矩阵的情况下被添加进去,那么不断添加这样的矩阵就可以了。

如果k≡2(mod4)k≡2(mod4)k2(mod4),不妨假设k≤n22k \le \frac{n^2}{2}k2n2,如果不满足,令k′=n2−kk^{'}=n^2-kk=n2k,构造完再全部取反即可。当k=2k=2k=2时,显然不存在满足条件的矩阵。当k=6k=6k=6时,可以构造出一个3×33 \times 33×3的矩阵,如下:

1 1 0
1 0 1
0 1 1

k≥6k≥6k6时,考虑将上面这个矩阵放在前444444列的位置,然后其他位置再通过添加2×22\times22×2的全111矩阵,即可构造满足要求的矩阵。

代码:

#include <bits/stdc++.h>

using namespace std;
typedef long long LL;
const int N = 1005;
int T, n, k;
bool mat[N][N];
int draw_first2block = 0;

void reset() {
    for (int i = 1; i <= n; ++i)
        for (int j = 1; j <= n; ++j)
            mat[i][j] = 0;
    draw_first2block = 0;
}

void draw() {
    mat[1][1] = mat[1][2] = mat[2][1] = mat[2][3] = mat[3][2] = mat[3][3] = 1;
    draw_first2block = 1;
}

void solve(int cnt) {
    int i = 1, j = 0;
    while (cnt) {
        if (++j > n / 2) {
            ++i;
            j = 1;
        }
        if (i <= 2 && j <= 2 && draw_first2block)
            continue;
        --cnt;
        mat[i * 2][j * 2] = mat[i * 2 - 1][j * 2] = mat[i * 2][j * 2 - 1] = mat[i * 2 - 1][j * 2 - 1] = 1;
    }
}

void reverse() {
    for (int i = 1; i <= n; ++i)
        for (int j = 1; j <= n; ++j)
            mat[i][j] ^= 1;
}

int main() {
    cin >> T;
    while (T--) {
        cin >> n >> k;
        if (n == 2 && k == 2) {
            cout << "Yes\n0 1\n1 0\n";
            continue;
        }
        bool check = false;
        if (k % 4 == 0) {
            check = true;
            reset();
            solve(k / 4);
        } else {
            bool reversed = false;
            if (k * 2 > n * n) {
                reversed = true;
                k = n * n - k;
            }
            if (k % 4 == 2 && k != 2) {
                check = true;
                reset();
                draw();
                solve((k - 6) / 4);
                if (reversed)
                    reverse();
            }
        }
        if (check) {
            cout << "Yes\n";
            for (int i = 1; i <= n; ++i) {
                for (int j = 1; j <= n; ++j)
                    cout << (mat[i][j] ? '1' : '0') << " \n"[j == n];
            }
        } else cout << "No\n";
    }
    return 0;
}

F.Construct Tree(bitset)

题意:

给定一个整数数组l1,l2,…,lnl_1,l_2,…,l_nl1,l2,,ln和一个整数ddd。是否能构造一棵满足以下三个条件的树:

  • 该树包含n+1n+1n+1个节点。
  • iii条边的长度等于lil_ili
  • 该树的(加权)直径等于ddd

分析:

考虑形式化地描述这个问题。先把lll排序。然后相当于是否存在一个1,2,…,n{1,2,…,n}1,2,,n的子集S,使得∑i∈Sli=d\sum\limits_{i∈S}{l_i}=diSli=d∃T⊆S∃T⊆STSmax(li)≤min(∑i∈Tli,∑i∈S∧i∉Tli)max(l_i)≤min(\sum\limits_{i\in T}{l_i},\sum\limits_{i\in S∧i\notin T}{l_i})max(li)min(iTli,iSi/Tli)

注意到若n−1∈S∧n∈Sn−1∈S∧n∈Sn1SnS,则第二个条件一定满足,让n−1∈Tn−1∈Tn1Tn∉Tn \notin Tn/T即可。所以如果l1,l2…ln−2l_1,l_2…l_n-2l1,l2ln2能凑出来d−an−1−and−a_{n−1}−a_ndan1an就可行。

然后依次讨论max(i)=n−1max(i)=n−1max(i)=n1i∉Si \notin Si/S)或nnn的情况:

  • max(i)=n−1max(i)=n−1max(i)=n1i∉Si \notin Si/S),那么n∈Sn∈SnS。若前n−2n−2n2个元素能凑出来和为xxxd−x−and−x−a_ndxan的两个不相交集合,且an−1≤min(x+an,d−x−an)a_{n-1}≤min(x+a_n,d−x−a_n)an1min(x+an,dxan)$就可行。
  • max(i)=nmax(i)=nmax(i)=ni∉Si \notin Si/S),那么若前n−1n−1n1个元素能凑出来和为xxxd−xd−xdx的两个不相交集合,且an≤min(x,d−x)a_n≤min(x,d−x)anmin(x,dx)就可行。

使用bitsetbitsetbitset进行优化。

代码:

#include <bits/stdc++.h>

using namespace std;
typedef long long LL;
const int maxn = 2005;

int n, m, a[maxn];
bitset<maxn> f[maxn], g;

void solve() {
    cin >> n >> m;
    for (int i = 1; i <= n; ++i) {
        cin >> a[i];
    }
    sort(a + 1, a + n + 1);
    for (int i = 0; i <= m; ++i) {
        f[i].reset();
    }
    g.reset();
    f[0].set(0);
    g.set(0);
    for (int i = 1; i < n; ++i) {
        if (i == n - 1) {
            if (a[n - 1] + a[n] <= m && g.test(m - a[n - 1] - a[n])) {
                cout << "Yes" << endl;
                return;
            }
            for (int j = 0; j <= m - a[n]; ++j) {
                if (a[i] <= min(j + a[n], m - a[n] - j) && f[j].test(m - j - a[n])) {
                    cout << "Yes" << endl;
                    return;
                }
            }
        }
        for (int j = m; ~j; --j) {
            f[j] |= (f[j] << a[i]);
            if (j >= a[i]) {
                f[j] |= f[j - a[i]];
            }
        }
        g |= (g << a[i]);
    }
    for (int i = a[n]; i <= m - a[n]; ++i) {
        if (f[i].test(m - i)) {
            cout << "Yes" << endl;
            return;
        }
    }
    cout << "No" << endl;
}

int main() {
    int T = 1;
    cin >> T;
    while (T--) {
        solve();
    }
    return 0;
}

学习交流

以下为学习交流QQ群,群号: 546235402,每周题解完成后都会转发到群中,大家可以加群一起交流做题思路,分享做题技巧,欢迎大家的加入。

### Codeforces Round 1005 Div. 2 A-F Problem Solutions #### A. Money Change 为了处理货币转换问题,可以将所有的金额都转化为分的形式来简化计算。通过遍历输入数据并累加各个部分的金额,最后求得剩余的钱数并对100取模得到最终结果[^2]。 ```cpp #include <iostream> using namespace std; int main() { int s, xi, yi; cin >> s; int total_cents = 0; for (int i = 0; i < s; ++i) { cin >> xi >> yi; total_cents += xi * 100 + yi; } cout << (s * 100 - total_cents) % 100 << endl; } ``` #### B. Odd and Even Pairs 此题目要求找到至少一对满足条件的索引:要么是一个偶数值的位置,或者是两个奇数值位置。程序会读入测试次数`t`以及每次测试中的数组长度`n`及其元素,并尝试找出符合条件的一对索引输出;如果没有这样的组合则返回-1[^3]。 ```cpp #include <cstdio> int main() { int t, n, num; scanf("%d", &t); while (t--) { int evenIndex = 0, oddIndex1 = 0, oddIndex2 = 0; scanf("%d", &n); for (int i = 1; i <= n; ++i) { scanf("%d", &num); if (num % 2 == 0 && !evenIndex) evenIndex = i; else if (num % 2 != 0) { if (!oddIndex1) oddIndex1 = i; else if (!oddIndex2) oddIndex2 = i; } if ((evenIndex || (oddIndex1 && oddIndex2))) break; } if (evenIndex) printf("1\n%d\n", evenIndex); else if (oddIndex1 && oddIndex2) printf("2\n%d %d\n", oddIndex1, oddIndex2); else printf("-1\n"); } return 0; } ``` 由于仅提供了前两道题的具体描述和解决方案,在这里无法继续给出完整的C至F题解答。通常情况下,每一道竞赛编程题都有其独特的挑战性和解决方法,建议查阅官方题解或社区讨论获取更多帮助。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符  | 博主筛选后可见
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值