Codeforces Round 876 (Div. 2) 题解 A - D

发布时间 2023-06-07 23:41:39作者: 叁纔

A. The Good Array

题目大意

给定两个整数 \(n\)\(k\),规定一个\(01\)数列为好的的条件是,对于\(1\sim n\)中任意的 \(i\),都有:

  • \(a\) 的前 \(i\) 个元素中至少有 \(\lceil\frac{i}k\rceil\) 个都是 \(1\)
  • \(a\) 的后 \(i\) 个元素中至少有 \(\lceil\frac{i}k\rceil\) 个都是 \(1\)

问数列中 \(1\) 的数量的最小可能值。

解题思路

先考虑最简单的情况,\(k\) 取得 \(1\),这样我们的每一个元素都必须为 \(1\)

如果 \(k\) 不是 \(1\),我们最终得到的序列一定是把 \(1\) 公摊使用的,那么前 \(k\) 个元素当中,只需要第一个元素是 \(1\) 即可,由前后缀的限制我们的数列也一定是对称的,依次类推,我们不难看出,实际上所求的数就是 \(\lceil\frac{n+k-1}{k}\rceil\)\(1\)

AC Code

#include <iostream>
#include <algorithm>
#include <cstring>
#include <iomanip>
#include <cmath>
#define endl '\n'
#define ios ios::sync_with_stdio(false), cin.tie(nullptr), cout.tie(nullptr)
using namespace std;
typedef long long LL;

const int N = 1e5 + 10;
const int MOD = 1e9 + 7;

void solve() {
    double n, k;
    cin >> n >> k;
    if (k == 1) {
        cout << n << endl;
        return;
    }
    cout << ceil((n + k - 1) / k) << endl;
}

signed main() {
    ios;
    int T = 1;
    cin >> T;
    while (T--) {
        solve();
    }
}

B. Lamps

题目大意

每个灯有打开,关闭和坏掉三种状态,以及两个参数 \(a\)\(b\)。其中, \(b\) 代表打开一盏灯的得点。\(a\) 代表一种制约关系,当场上打开了 \(x\) 盏灯,那么场上所有 \(a\) 值小于 \(x\) 的灯会同时坏掉,不可以再次操作。

问最后最大得点为多少。

解题思路

首先我们来看看最优情况是怎么样的。

如果存在一个 \(a\) 值很小,比方说是 \(1\),然后 \(b\) 值很大的一盏灯,那么我们肯定会优先开启这盏灯,然后它会坏掉,同时保留这盏灯的得点。

那么我们可以对这些灯按照 \(a\) 值分类,在每一类中对 \(b\) 值由大到小排序,随后只需要暴力找每个值即可。

AC Code

#include <iostream>
#include <algorithm>
#include <cstring>
#define endl '\n'
#define ios ios::sync_with_stdio(false), cin.tie(nullptr), cout.tie(nullptr)
using namespace std;
typedef long long LL;

const int N = 2e5 + 10;
const int MOD = 1e9 + 7;

void solve() {
    int n;
    LL res = 0;
    cin >> n;
    vector<vector<int>> v(n + 1);
    for (int i = 1; i <= n; ++i) {
        int x, y;
        cin >> x >> y;
        v[x].push_back(y);
    }
    for (int i = 1; i <= n; ++i) {
        std::sort(v[i].begin(), v[i].end(), greater<>());
        for (int j = 0; j < min(i, (int)v[i].size()); ++j) {
            res += v[i][j];
        }
    }
    cout << res << endl;
}

signed main() {
    ios;
    int T = 1;
    cin >> T;
    while (T--) {
        solve();
    }
}

C. Insert Zero and Invert Prefix

题目大意

给定一个\(01\)数列 \(a\),同时初始化一个空\(01\)数列 \(b\),现在需要进行 \(n\) 次操作,每次操作可以选择 \(i \in [0, b.\text{length - 1}]\),将 \(b\)\([0,i]\) 的元素取反,并在第\(i\) 位插入 \(0\),后面的元素不动。问能否操作构造出 \(a\) 数列。

解题思路

从零构造不好做的话我们可以考虑正难则反,考虑逆向情况。

题目转化为删去 \(a\) 中的某个 \(0\),然后将它前面的元素取反。

问能否通过恰好 \(n\) 次操作把 \(a\) 删干净。

我们可以双指针模拟这个过程,因为我们要取得最优情况一定是删去最靠前的 \(0\),这样才能保证每次操作更改的数最少。

需要注意的是我们操作后,数列元素个数有变化,所以他们的编号也有变化,需要使用位置变量来存一下。

AC Code

#include <iostream>
#include <algorithm>
#include <cstring>
#define endl '\n'
#define ios ios::sync_with_stdio(false), cin.tie(nullptr), cout.tie(nullptr)
using namespace std;
typedef long long LL;

const int N = 1e5 + 10;
const int MOD = 1e9 + 7;

int a[N], res[N];

void solve() {
    int n;
    cin >> n;
    for (int i = 1; i <= n; ++i) {
        cin >> a[i];
    }
    int cnt = 0, pos = 1;
    for (int i = 1; i <= n; ++i) {
        if (!a[i]) {
            res[++cnt] = i - pos;
            for (int j = 1; j <= i - pos; ++j) {
                res[++cnt] = 0;
            }
            pos = i + 1;
        }
    }
    if (cnt != n) {
        cout << "NO" << endl;
        return;
    }
    cout << "YES" << endl;
    for (int i = n; i >= 1; --i) {
        cout << res[i] << ' ';
    }
    cout << endl;
}

signed main() {
    ios;
    int T = 1;
    cin >> T;
    while (T--) {
        solve();
    }
}

D. Ball Sorting

题目大意

\(n\) 个彩色球排成一列。这些球被涂成了 \(n\) 种不同的颜色,用数字从 \(1\)\(n\) 表示。从左边数第 \(i\) 个球涂成颜色 \(c_i\)。现在需要重新排列这些球,使得从左边数第 \(i\) 个球的颜色为 \(i\) 。此外,你有 \(k\ge 1\) 个颜色为 \(0\) 的球,可以在重新排列过程中使用。

现在允许如下操作:

  1. 在序列的任意位置放置一个颜色为 \(0\) 的球,同时保持其他球的相对顺序。最多可以执行这个操作 \(k\) 次。
  2. 选择任意一个非零颜色的球同时至少有一个与它相邻的球颜色为 \(0\),将该非零颜色的球移到序列的任意位置,同时保持其他球的相对顺序不变。你可以无限次执行这个操作,但是每次操作你需要支付 \(1\) 个硬币。

操作结束之后颜色为零的球会消失,问排序完成的最小代价是多少。

解题思路

首先看本题的数据范围,最大只到 \(500\),那么 \(O(n^3)\) 的dp可解。

本题实际上是全排列的排序问题,我们颜色为 \(0\) 的球可以让他相邻两边的球归位,而当这个球走了,\(0\) 就会与新的球相邻,那么每个 \(0\) 实际上可以控制一段连续的序列。那么我们可以dp一个LIS(最长上升子序列),同时计算不处于LIS的部分被分成了几段即可。

AC Code

#include <iostream>
#include <algorithm>
#include <cstring>
#define endl '\n'
#define ios ios::sync_with_stdio(false), cin.tie(nullptr), cout.tie(nullptr)
using namespace std;
typedef long long LL;

const int N = 510;
const int MOD = 1e9 + 7;

int a[N];
int dp[N][N];
LL res[N];

void solve() {
    memset(dp, -0x3f, sizeof dp);
    int n;
    cin >> n;
    for (int i = 1; i <= n; ++i) {
        cin >> a[i];
    }
    a[n + 1] = 0x3f3f3f3f;
    dp[1][0] = 1;
    for (int i = 2; i <= n; i++) {
        dp[i][1] = 1;
    }
    for (int i = 2; i <= n + 1; i++) {
        for (int k = 0; k <= i + 1; k++) {
            for (int j = 1; j < i; j++) {
                if (a[j] < a[i]) {
                    if (j == i - 1) {
                        dp[i][k] = max(dp[i][k], dp[j][k] + 1);
                    } else if (k > 0) {
                        dp[i][k] = max(dp[i][k], dp[j][k - 1] + 1);
                    }
                }
            }
        }
    }
    for (int k = 1; k <= n; ++k) {
        dp[n + 1][k] = max(dp[n + 1][k], dp[n + 1][k - 1]);
        cout << n + 1 - dp[n + 1][k] << ' ';
    }
    cout << endl;
}

signed main() {
    ios;
    int T = 1;
    cin >> T;
    while (T--) {
        solve();
    }
}