Contestant. Rank 2570. Rating +70.

A. GamingForces

题意

给定一个数组 \(a\),定义操作可任选其一:

  1. 将其中两个元素减 \(1\)
  2. 将某个元素减为 \(0\)

输出最少的操作数,使 \(a\) 的所有元素都减为 \(0\)

思路

将所有为 \(1\) 的元素配对,扣去偶数个 \(1\) 后,剩下的元素全都执行操作 \(2\) 即可。

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

对应AC代码

import java.util.*;

public class Main{
    public static void main(String[] args){
        Scanner scanner = new Scanner(System.in);
        int t = scanner.nextInt();
        while(t -- > 0){
            int n = scanner.nextInt();
            int cnt = 0;
            for(int i=0;i<n;i++) if(scanner.nextInt() == 1) cnt ++;
            System.out.println(n - cnt / 2);
        }
    }

}

简单签到题

B. Stand-up Comedian

题意

给定整数 \(a_1\)\(a_2\)\(a_3\)\(a_4\)\(4\) 个整数代表了四个类型对应的笑话数量:

  1. \(A\)\(B\) 都喜欢
  2. \(A\) 喜欢,\(B\) 不喜欢
  3. \(B\) 喜欢,\(A\) 不喜欢
  4. \(A\)\(B\) 都不喜欢

初始状态下,两人的心情值都为 \(0\),对于喜欢的笑话,心情会 \(+1\),否则会 \(-1\)

输出最多能讲多少个笑话。

思路

模拟+思维

首先,我们先把类型 \(1\) 讲完,如果 \(a_1=0\),直接判 \(1\) 个。

接着,我们讨论 \(|a_2-a_3|\)\(a_1\) 的大小关系,若 \(a_1\) 较小,那么笑话是讲不完的,只有 \(2 \times (a_1+\min(a_2,a_3)) + 1\) 个笑话可以讲。

否则,将 \(|a_2-a_3|\) 讲完后,判断剩余的可以讲多少个 \(a_4\) 即可。

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

对应AC代码

import java.util.*;

public class Main{
    public static void main(String[] args){
        Scanner scanner = new Scanner(System.in);
        int t = scanner.nextInt();
        while(t -- > 0){
            int[] a = new int[4];
            for(int i=0;i<4;i++) a[i] = scanner.nextInt();
            if(a[0] == 0){
                System.out.println(1);
                continue;
            }
            int n = Math.min(a[1], a[2]), ans = 0;
            ans += a[0] + n * 2;
            int m = Math.max(a[1] - n, a[2] - n);
            if(a[0] < m){
                System.out.println(2 * (a[0] + n) + 1);
            }else{
                ans += m;
                ans += Math.min(a[0] - m + 1, a[3]);
                System.out.println(ans);
            }
        }
    }

}

思路好乱

C. Min Max Sort

题意

给定一个排列 \(p\),定义一次操作为任选两个元素,将其从原排列中取出,将较小元素放至排列头部,较大元素放至排列尾部,输出最小的操作数,使最后的排列升序。

思路

逆向思维

显然,我们需要对形如 \([i,n-i+1]\) 的元素对进行操作。

考虑到一个不需要移动的元素对,应满足三个条件:

  1. \(i\)\(n-i+1\) 前面
  2. \(i\)\(i+1\) 前面
  3. \(n-i\)\(n-i+1\) 前面

筛出所有不需要移动的元素对并取补集即可。

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

对应AC代码

import java.util.*;

public class Main{
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        int t = scanner.nextInt();
        while (t-- > 0) {
            int n = scanner.nextInt();
            int[] w = new int[n + 1];
            int ans = n / 2;
            for (int i = 1; i <= n; i++) w[scanner.nextInt()] = i;
            for (int i = n / 2; i >= 1; i--) { //排除不用移动的
                if (w[i] < w[i + 1] && w[n - i + 1] > w[n - i] && w[i] < w[n - i + 1]) ans--;
                else break;
            }
            System.out.println(ans);
        }
    }

}

反着想简单多了

D. Fixed Prefix Permutations

题意

给定整数 \(n\)\(m\),定义两个排列 \(p\)\(q\) 的乘积为一个新的排列 \(r\),其中 \(r_j=q_{p_j}\)。给定长度为 \(m\)\(n\) 个排列,对于每一个排列,输出其与所有 \(n\) 个排列的乘积构成的排列的最大美丽值。其中美丽值定义为满足 \(p_1=1,p_2=2,...,p_k=k\)\(k\) 的最大值(\(p_1 \neq 1\)时美丽值为 \(0\))。

思路

显然,对于给定的排列 \(p\)\(q\),若已知 \(q\),那么所构造出来的 \(r\)\(p\) 是一一对应的。所以,我们可以将问题转化一下:

对于所有 \(q\),先预处理构造出满足 \(k=m\) 的排列 \(p_t\) ,然后对于每一个 \(p\) ,在所有 \(p_t\) 中依次匹配,输出从第一个开始最大的连续匹配数量。

为了让匹配的复杂度降到最低,我们可以采用字典树

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

对应AC代码

#include <bits/stdc++.h>

using namespace std;

const int N = 50010;

int n, m;
int a[N][20], tmp[20];

class Trie {
private:
    vector<Trie*> children;

public:
    Trie() : children(12){}

    void insert() {
        Trie* node = this;
        for (int i=0;i<m;i++) {
            int ch = tmp[i];
            if (node->children[ch] == nullptr) {
                node->children[ch] = new Trie();
            }
            node = node->children[ch];
        }
    }

    int find(int index) {
        Trie* node = this;
        int dep = 0;
        for (int i=0;i<m;i++) {
            int ch = a[index][i];
            if (node->children[ch] == nullptr) {
                return dep - 1;
            }
            node = node->children[ch];
            dep ++;
        }
        return dep;
    }

};

int main() {
    ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
    int t;
    cin >> t;
    while(t --){
        Trie root = *new Trie();
        cin >> n >> m;
        for(int i=0;i<n;i++){
            for(int j=1;j<=m;j++){
                cin >> a[i][j];
                tmp[a[i][j]] = j;
            }
            root.insert();
        }
        for(int i=0;i<n;i++){
            cout << root.find(i) << " ";
        }
        cout << '\n';
    }
}

题目真的很难看懂...