Educational Codeforces Round 183 (Rated for Div. 2) 题解

A

直接提前分给三人,看看余下多少,然后用 3 去相减就行了。不过注意 3 的倍数的情况是 0。

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

int n;

void solve () {
    cin >> n;
    int res = n%3;
    if (!res) cout << "0\n";
    else cout << 3-res << "\n";
}

int main () {
    ios::sync_with_stdio(false);
    cin.tie(nullptr), cout.tie(nullptr);
    int _ = 1; cin >> _;
    while (_--) solve();
    return 0;
}

B

挺有意思的一道题目。

其实通过手推我们不难发现,如果有一个操作是 2,那么只要后面跟了个 0 或者 1,只能保证将 2 带来的 ? 再往里面缩一格,因为此时只能保证原来的最外面的那一层被 0 或者 1 带走,但是里面一层的情况未知。

所以我们可以发现规律,我们先统计出所有的 0 和 1 的数量,提前操作出 -,然后再在剩下的里面填入 ? 以及 +,但是注意,如果剩下的牌的数量如果 \(\leq cnt_2\),那么剩下的牌也一定会被全部删除,可以全部填上 -

#include <bits/stdc++.h>
using namespace std;
#define ll long long
#define ull unsigned long long
const int N = 2e5+100;

int n, k;
char ans[N];

void solve () {
    cin >> n >> k;
    for (int i = 1;i <= n;i++) ans[i] = ' ';
    string s; cin >> s;
    int zero = 0, one = 0, two = 0, l = 1, r = n;
    for (int i = 0;i < k;i++) {
        if (s[i] == '0') zero++;
        else if (s[i] == '1') one++;
        else two++;
    }

    for (int i = 1;i <= zero;i++) ans[l++] = '-';
    for (int i = 1;i <= one;i++) ans[r--] = '-';
    int res = n-zero-one;
    if (res <= two) 
        for (int i = l;i <= r;i++) ans[i] = '-';
    else {
        for (int i = 1;i <= two;i++) ans[l++] = '?';
        for (int i = 1;i <= two;i++) ans[r--] = '?';
        for (int i = l;i <= r;i++) ans[i] = '+';
    }
    for (int i = 1;i <= n;i++) cout << ans[i];
    cout << "\n";
}

int main () {
    ios::sync_with_stdio(false);
    cin.tie(nullptr), cout.tie(nullptr);
    int _ = 1; cin >> _;
    while (_--) solve();
    return 0;
}

C

有意思的前缀和。

不难发现,我们假设有 \(cnt_a, cnt_b\),题目的要求就是找到一段连续的子串使得当前这一段的 \(|cnt_a - cnt_b| == |cnt_{total_a} - cnt_{total_b}|\)

所以我们可以考虑前缀和,用 \(O(n)\) 的复杂度统计,然后记录每一个前缀和的最近的出现位置,然后每看到一个前缀和 \(pre\),我们就去找 \(k-pre\) 的位置,这样子可以以贪心的思想满足题目要求。

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

void solve() {
    int n; string s;
    cin >> n >> s;
    int cnta = 0, cntb = 0;
    for (int i = 0;i < n;i++) {
        if (s[i] == 'a') cnta++;
        else cntb++;
    }
    if (cnta == cntb) {
        cout << 0 << endl;
        return;
    }
    
    int need = cnta - cntb;
    unordered_map<int, int> pos;
    int tmp = 0;
    pos[0] = -1;
    int minn = INT_MAX;
    for (int i = 0; i < n; i++) {
        if (s[i] == 'a') tmp++;
        else tmp--; 
        int needed = tmp - need;
        if (pos.count(needed)) {
            int len = i - pos[needed];
            if (len < n) minn = min(minn, len);
        }
        
        pos[tmp] = i;
    }
    
    if (minn == INT_MAX) 
        cout << -1 << "\n";
    else 
        cout << minn << "\n";
    return;
}

int main() {
    ios::sync_with_stdio(false);
    cin.tie(NULL), cout.tie(nullptr);
    int _ = 1; cin >> _;
    while (_--) solve();
    return 0;
}

D

比较难的一道 dp,赛时也是被大佬指点迷津才做出来的。

首先分析题目的 反转值,发现只有递增的数组才不会有反转值的贡献,于是我们考虑有 \(m\) 个只递增的数组,每段的长度为 \(l_1, l_2, l_3 \dots l_m\)

然后发现,总数组的子数组个数为 \(\frac{n\times(n+1)}{2}\),而所有递增的子数组的子数组个数为 \(\sum_{i = 1}^m\frac{l_i\times(l_i+1)}{2}\),于是想到容斥原理,该总数组的反转值就为 \(\frac{n\times(n+1)}{2} - \sum_{i = 1} ^ m\frac{l_i\times(l_i+1)}{2} = k\)

发现这个思路可以试试 dp,于是开始考虑 dp 的状态,将其设为:

\(dp_{i, j}\) 表示用 \(i\) 个数字能否得到 \(j\) 个递增子数组。

\(g_{i, j}\) 表示上面那种情况时最后一段的长度,这可以方便后面的划分。

那么我们所需要的递增数组可以通过移项得到 \(\frac{n\times(n+1)}{2} - k\),那么我们想到这样的转移方式:

对于每个i从1到n
  对于每个j从0到need
    对于每个len从1到i
      cost = len*(len-1)/2
      如果j≥cost且dp[i-len][j-cost]存在
        则dp[i][j] = true, g[i][j] = len

那么我们继续想这种转移方程是否是正确的,发现 \(n \leq 30\),完全不怕超时,直接拿下。

前面也说了 \(g_{i, j}\) 是状态 \(i, j\) 时最后一段的长度,所以我们可以考虑回溯构造,一遍遍回溯状态的同时将我们存储的长度存进去,因为是回溯,所以我们最后需要倒转一下:

vector<int> res;
int num = n;
for (int len : seg) {
	for (int j = num - len + 1; j <= num; j++) {
		res.push_back(j);
	}
	num -= len;
}

for (int i = 0; i < n; i++) {
	cout << res[i];
	if (i < n - 1) cout << " ";
}
cout << endl;

最后就是完整代码:

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

const int N = 35;
const int S = 435;

bool f[N][S + 5];
int g[N][S + 5];

void solve() {
    int n, k;
    cin >> n >> k;
    
    int total = n * (n - 1) / 2;
    
    if (k > total) {
        cout << 0 << endl;
        return;
    }
    
    int need = total - k;
    
    memset(f, false, sizeof(f));
    memset(g, -1, sizeof(g));
    f[0][0] = true;
    
    for (int i = 1; i <= n; i++) {
        for (int j = 0; j <= need; j++) {
            for (int len = 1; len <= i; len++) {
                int cost = len * (len - 1) / 2;
                if (j >= cost && f[i - len][j - cost]) {
                    f[i][j] = true;
                    g[i][j] = len;
                    break;
                }
            }
        }
    }
    
    if (!f[n][need]) {
        cout << 0 << endl;
        return;
    }
    
    vector<int> seg;
    int cur = n, val = need;
    while (cur > 0) {
        int len = g[cur][val];
        seg.push_back(len);
        cur -= len;
        val -= len * (len - 1) / 2;
    }
    
    reverse(seg.begin(), seg.end());
    
    vector<int> res;
    int num = n;
    for (int len : seg) {
        for (int j = num - len + 1; j <= num; j++) {
            res.push_back(j);
        }
        num -= len;
    }
    
    for (int i = 0; i < n; i++) {
        cout << res[i];
        if (i < n - 1) cout << " ";
    }
    cout << endl;
}

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr), cout.tie(nullptr);
    int _ = 1; cin >> _;
    while (_--) solve();
    return 0;
}