Practice.

A. Two Groups

题意

给定一个数组 \(a\),可以为负数,从数组 \(a\) 中取出某些数作为序列 \(s_1\),剩余作为序列 \(s_2\),输出 \(|sum(s_1)| - |sum(s2)|\) 的最大值。序列可以为空。

思路

考虑到 \(|sum(s_1)| - |sum(s2)| \leq |sum(s_1) + sum(s2)| = |sum(a_i)|\),我们可以发现原数组的总和的绝对值即为最大值。

时间复杂度:\(O(n)\)

对应AC代码

#include <bits/stdc++.h>

using namespace std;

#define int long long

const int N = 2e5+10;

signed main(){
    ios::sync_with_stdio(false), cin.tie(NULL), cout.tie(NULL);
    int t;
    cin >> t;
    while(t --){
        int n;
        cin >> n;
        int ans = 0;
        for(int i=1;i<=n;i++) {
            int cur;
            cin >> cur;
            ans += cur;
        }
        cout << abs(ans) << '\n';
    }
}

暴力枚举碰运气过了可还行

B. BAN BAN

题意

给定一个整数 \(n\),对于由 \(n\)\(BAN\) 组成的字符串,输出至少交换几次,使所有长度为 \(3\)可不连续子序列不包含 \(BAN\)

思路

显然,我们希望 \(B\) 能移到后面,\(N\) 能移到前面,那么我们不妨将第 \(i\)\(B\) 和第 \(n - i + 1\)\(N\) 交换。

当然,若 \(n\) 为奇数,中间的那个字符串直接反转即可。

时间复杂度:\(O(n)\)

对应AC代码

#include <bits/stdc++.h>

using namespace std;

#define int long long

const int N = 2e5+10;

signed main(){
    ios::sync_with_stdio(false), cin.tie(NULL), cout.tie(NULL);
    int t;
    cin >> t;
    while(t --){
        int n;
        cin >> n;
        cout << n / 2 + n % 2 << '\n';
        for(int i=0;i<n/2;i++) cout << 1 + i * 3 << ' ' << 3 * n - (i * 3) << '\n';
        if(n % 2 == 1) cout << 3 * (n / 2) + 1 << ' ' << 3 * (n / 2) + 3 << '\n';
    }
}

纯纯找规律((

C. Swap Game

题意

给定一个序列,规定每个玩家可以选择一个数,将其与第一个数交换,并将第一个数扣去 \(1\)。定义最后将 \(0\) 交换到第一个位置上的玩家输,在 \(Alice\) 先手的条件下,输出最后的赢家。

思路

首先,若最小的数是第一个数,那么先手就会将最小的数放到后面,从而使后手具有必胜策略:我只需每次都挑大的数,那么对方就只能挑小的数,从而让对方最后只能取 \(0\)。反之同理。

时间复杂度:\(O(n)\)

对应AC代码

#include <bits/stdc++.h>

using namespace std;

#define int long long

const int N = 2e5 + 10, inf = 0x3f3f3f3f;

signed main(){
    ios::sync_with_stdio(false), cin.tie(nullptr), cout.tie(nullptr);
    int t;
    cin >> t;
    while(t --){
        int n;
        cin >> n;
        int minn = inf, a0;
        cin >> a0;
        minn = min(minn, a0);
        for(int i=1;i<n;i++){
            int cur;
            cin >> cur;
            minn = min(minn, cur);
        }
        cout << (minn == a0 ? "Bob" : "Alice") << '\n';
    }
}

当然和总和的奇偶性没关系

D. Yet Another Problem

题意

给定一个序列 \(a\),定义一次操作为选定任意长度为奇数的区间,将区间内的数全都改为整个区间的异或值。对于每个区间询问,输出最小操作数,使区间的数都变为 \(0\)。无解输出 \(-1\)

思路

首先,若没有区间奇偶性的限制,我们直接预处理出前缀异或,然后用类似于前缀和的方式计算即可。

当然,也可以用一下前缀和,毕竟我们需要知道这个区间是否已经全为 \(0\)

若查询的区间长度为偶数,那么我们只能挑选两个长度为奇数的区间。考虑到对一个子区间操作后,剩余的区间的异或值一定为 \(0\),所以我们只需枚举所有长度为奇数的右端的子区间即可(左端也行)。

不过,我们不可以枚举,因为数据量实在太大了。所以,我们需要进行预处理。对于一个右端点,若它为偶数,那么以它为右端点的区间的左端点一定是奇数,从而我们可以进行递推,找到两个异或值相等且奇偶性不同的点即可。

时间复杂度:\(O(n)\)

对应AC代码

#include <bits/stdc++.h>

using namespace std;

#define int long long

const int N = 2e5 + 10;

int a[N], xo[N], sum[N], pre[N];

signed main() {
    ios::sync_with_stdio(false), cin.tie(nullptr), cout.tie(nullptr);
    int n, q;
    cin >> n >> q;
    vector<map<int, int>> mp(2);
    for (int i = 1; i <= n; i++) {
        cin >> a[i];
        xo[i] = xo[i - 1] ^ a[i];
        sum[i] = sum[i - 1] + a[i];
        if(mp[1 - i % 2].count(xo[i])) pre[i] = mp[1 - i % 2][xo[i]];
        mp[i % 2][xo[i]] = i;
    }
    while (q--) {
        int l, r;
        cin >> l >> r;
        if ((xo[r] ^ xo[l - 1]) != 0) {
            cout << -1 << '\n';
            continue;
        }
        if (sum[r] - sum[l - 1] == 0) {
            cout << 0 << '\n';
        } else if ((r - l + 1) % 2 == 1) {
            cout << 1 << '\n';
        } else {
            if (a[r] == 0 || a[l] == 0) {
                cout << 1 << '\n';
                continue;
            }
            cout << (pre[r] >= l ? 2 : -1) << '\n';
        }
    }
}

二分半天不过...