Codeforces Round 1059 (Div. 3) (A~E) 题解

A

题意就是让你找所有 \([l, r]\) 中平均值的最大值,因为数据量较小,模拟即可。

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

void solve () {
    int n; cin >> n;
    vector<int> a(n+1);
    for (int i = 1;i <= n;i++) cin >> a[i];
    ll sum = 0;
    for (int i = 1;i <= n;i++) {
        ll tmp = 0;
        for (int j = i;j <= n;j++) {
            tmp += a[j];
            sum = max(sum, tmp/(j-i+1));
        }
    }
    cout << sum << "\n";
}

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

B

题目给了一串 01 串,然后告诉你要在其中选择任意个字符删掉,且这任意个字符按照原来的顺序组成的数组要是非递减的,并且剩余的 01 串不改变原来的顺序时应该是个回文串。

也是很简单,不难发现因为是 01 串,所以我们可以直接全部删掉 0 或者 1 即可。

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

void solve () {
    int n; cin >> n;
    string s; cin >> s;
    int cnt = 0;
    for (int i = 0;i < n;i++) 
        if (s[i] == '0') cnt++;
    cout << cnt << "\n";
    for (int i = 0;i < n;i++) 
        if (s[i] == '0') cout << i+1 << " ";
    cout << "\n";
}

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

C

人类异或题。

对于 \(a\)\(b\) 我们都可以对其拆解成一个二进制串,设为 \(a_1、b_1\),不难想到当 \(|a_1| < |b_1|\) 时会无解。

同时我们考虑 \(|a_1| \geq |b_1|\) 的情况,先思考能不能一次性得到结果,考虑到异或的性质允许我们移项,即 \(x \oplus y = z\) 可以变为 \(x = z \oplus y\),但是想到当 \(|a_1| > |b_1|\) 时用这个方法取的数会出现 \(> a\) 的情况,所以我们考虑分两次,并假设这个是通用解。

然后发现,可以先对第 \(0\) 位到第 \(|b_1|-1\) 位的两者的二进制表达式比较不同得到第一个结果,然后再单独对第 \(|b_1|\) 位到 \(|a_1|-1\) 位的两者的二进制表达式进行同样的操作,这样就可以保证取得的两个数都 \(< a\),此时答案已经呼之欲出。

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

void solve () {
    ll a, b;
    cin >> a >> b;
    if (a == b) { cout << "0\n"; return; }
    string _a = "", _b = "";
    ll tmpa = a, tmpb = b;
    while (tmpa) {
        _a += (tmpa%2+'0');
        tmpa /= 2;
    }
    while (tmpb) {
        _b += (tmpb%2+'0');
        tmpb /= 2;
    }
    if (_b.size() > _a.size()) { cout << "-1\n"; return; }
    // for (ll i = 1;i <= (_a.size()-_b.size());i++) _b += "0";
    ll ansa = 0, ansb = 0;
    cout << "2\n";
    for (ll i = _b.size()-1;i >= 0;i--) {
        if (_a[i] == '1' && _b[i] == '0') ansa += pow(2, i);
        else if (_a[i] == '0' && _b[i] == '1') ansa += pow(2, i);
    }
    for (ll i = _a.size()-1;i > _b.size()-1;i--) 
        if (_a[i] == '1') ansb += pow(2, i);
    cout << ansa << " " << ansb << "\n";
}

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

D

好玩的交互题,现场学习交互的操作真是类似我哩。

题目其实很好理解:我们有一个长度为 \(n\) 的排列 \(p\)。然后有人秘密选择了区间 \([l, r]\),并在这个区间内的每个元素上加 \(1\),得到修改后的数组 \(a\)。然后我们要通过不超过40次查询,找出这个秘密区间 \([l, r]\)

然后不难发现 \(|\sum_{i = l}^ra_i - \sum_{i = l}^rp_i|\) 就为这个区间同秘密区间的交集长度。于是我们可以直接用 \(|\sum_{i = 1}^na_i - \sum_{i = 1}^np_i|\) 得到区间的长度 \(k\)

然后我们发现这个区间似乎具有某种特定的单调性,通过观察和猜想,发现我们可以通过二分端点来确定区间的左端点,具体来讲就是二分端点 \(i\)

  • \(\sum_{j=1}^ip_j - \sum_{j=1}^ia_j > 0\) 说明左端点在 \(i\) 的左边。
  • 否则在 \(i\) 的右边。

然后 \(r\) 就可以通过长度 \(k\)\(l\) 来确定了。

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

void solve() {
    int n; cin >> n;
    auto q1 = [](int l, int r) {
        cout << "1 " << l << " " << r << endl;
        ll res; cin >> res; return res;
    };
    auto q2 = [](int l, int r) {
        cout << "2 " << l << " " << r << endl;
        ll res; cin >> res; return res;
    };
    ll k = q2(1, n) - q1(1, n);
    int l = 1, r = n, pos = -1;
    while (l <= r) {
        int mid = (l + r) / 2;
        ll diff = q2(1, mid) - q1(1, mid);
        if (diff == 0) l = mid + 1;
        else { pos = mid; r = mid - 1; }
    }
    cout << "! " << pos << " " << pos + k - 1 << endl;
}

int main() {
    ios_base::sync_with_stdio(false); cin.tie(0);
    int _; cin >> _;
    while (_--) solve();
    return 0;
}

E

细节还是蛮恶心的(我认为)。

不难发现我们可以分成两种情况:

  • 数组是个排列。
  • 数组不是个排列。

而且我们发现,我们加进来的数无法改变前面原数组已有的回文子串,只能保证加进来的数字不会和加进来的数字或者原数组的数字构成新的回文串,只是自己单独一个是回文串。

此时分类讨论:

  • 当数组是排列时,我们只需要注意我们在后面加进来的第一个和第二个数分别不能和原数组的倒数第二个和倒数第一个构成回文、不能和原数组倒数第一个和加进来的第一个数构成回文即可,然后后面只需要随意输出出了加进来的第一和第二个数即可,此时无法在和前面的构成回文。

  • 当数组不是排列时,我们可以统计出哪些数字没出现过,然后加进去,加完没出现过的数字再把出现过的数字加进去就行。但是我们还有一个特判,当我们没出现过的数字只有一个而要求加的数字有两个时,我们要注意我们加进去的第一个出现过的数字也就是加进去的第二个数字不能和原数组的最后一个数字构成回文,举个例子:

6 3
6 5 4 3 2 2

代码:

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

void solve () {
    int n, k; cin >> n >> k;
    vector<int> a(n+1);
    vector<bool> check(n+1, false);
    for (int i = 1;i <= n;i++) {
        cin >> a[i];
        check[a[i]] = true;
    }
    bool flag = false;
    for (int i = 1;i <= n;i++) 
        if (!check[i]) { flag = true; break; }
    if (flag) {
        int cnt = 0;
        for (int i = 1;i <= n;i++) {
            if (cnt >= k) { cout << "\n"; return; }
            if (!check[i]) { cout << i << " "; cnt++; }
        }
        if (cnt == 1) {
            for (int i = 1;i <= n;i++) 
                if (check[i] && i != a[n]) { check[i] = false; cout << i << " "; break; }
            cnt++;
        }
        for (int i = 1;i <= n;i++) {
            if (cnt >= k) { cout << "\n"; return; }
            if (check[i]) { cout << i << " "; cnt++; }
        }
        cout << "\n"; return;
    }
    int lst = 0;
    for (int i = 1;i <= n;i++) 
        if (i != a[n] && i != a[n-1]) { lst = i, check[i] = false; cout << i << " "; break; }
    if (k == 1) { cout << "\n"; return; }
    for (int i = 1;i <= n;i++) 
        if (i != a[n] && i != lst) { check[i] = false; cout << i << " "; break; }
    int cnt = 2;
    for (int i = 1;i <= n;i++) {
        if (cnt >= k) { break; }
        if (check[i]) { cout << i << " "; cnt++; }
    }
    cout << "\n";
}

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