Contestant. Rank 4776. Rating -20 (+30 -50).

A. Parallel Projection

题意

给定一个长方体,在长方体的顶部和底部各取一个点,一只蚂蚁只能在平面上向平行于边的方向移动,求出蚂蚁从一个点移动到另一个点的最短路径。

思路

从四个方向分别模拟一下求最小值即可。

时间复杂度:\(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 w = scanner.nextInt(), d = scanner.nextInt(), h = scanner.nextInt();
            int a = scanner.nextInt(), b = scanner.nextInt(), f = scanner.nextInt(), g = scanner.nextInt();
            int p1 = Math.min(a + f + Math.abs(g - b), b + g + Math.abs(f - a));
            a = w - a;
            b = d - b;
            f = w - f;
            g = d - g;
            int p2 = Math.min(a + f + Math.abs(g - b), b + g + Math.abs(f - a));
            System.out.println(h + Math.min(p1, p2));
        }
    }
}

还是用蚂蚁理解更加经典((

B. Going to the Cinema

题意

给定数组 \(a\),对于任意 \(i\),提出一个要求:“我只在 不算上我的前提下 至少有 \(a_i\) 个人陪我一起去 的前提下去电影院,否则我会难过。”

注意考虑逆否命题:“我不去电影院,而且只有不到 \(a_i\) 个人抛下我去电影院,我依然是开心的,否则我也会难过。”

输出所有不让任何人伤心的安排的总数。

思路

首先,显然我们一定得让 \(a_i=0\) 的人全都去电影院,不然逆否命题一定不成立。

接着,我们遍历剩下按升序排序的 \(a_i\),同理找出必须去的人,在找出第一个不一定要去的人的时候停止遍历。

然后,我们遍历后面可以不选上的人,并在选上后判断后者是否必须去。

最后,所有可以不选上的人的个数即为答案,因为若在递增序列里间隔选人,会导致命题不成立。

时间复杂度:\(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[] a = new int[n];
            for (int i = 0; i < n; i++) a[i] = scanner.nextInt();
            Arrays.sort(a);
            int c = 0, i = 0, ans = 0;
            for(;i<n;i++){
                if(a[i] == 0) c ++;
                else if(a[i] <= c) c ++;
                else break;
            }
            ans++;
            while (i++ < n) {
                c++;
                if (a[i] <= c) {
                    ans++;
                    while (i < n && a[i] <= c) {
                        i++;
                        c++;
                    }
                }
            }
            System.out.println(ans);
        }
    }
}

云里雾里

C. Equal Frequencies

题意

给定一个字符串 \(s\),定义一次操作为替换 \(s_i\) 为任意其他字母,输出操作数最少的结果,使结果中所有字母出现的次数均相同。

思路

考虑到小写字母只有 \(26\) 个,我们不妨枚举所有可能的段数,找出操作数最少的情况然后输出即可。

如何判断操作数最少呢?对于每个字母 \(c\),我们不难发现:\(\min(\frac{n}{k},freq_c)\) 即为当前需要保留的数量。因此,去除保留的数量,剩余即为操作数。

确定段数之后,我们先将字符串中各个字母按照出现次数降序排序,然后从头开始遍历每一个字母,若字母数量过多,那么将多出来的位置留空,如果过少,那么在留空的位置填上缺少的字母。最后将序列输出即可。

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

对应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();
            char[] o = scanner.next().toCharArray();
            List<Pair<Integer, List<Integer>>> f = new ArrayList<>();
            for(int i=0;i<26;i++) f.add(new Pair<>(i, new ArrayList<>()));
            for(int i=0;i<n;i++) f.get(o[i] - 'a').B.add(i);
            f.sort(Comparator.comparingInt(o1 -> -o1.B.size()));
            int best = 0, cnt = 1, cur;
            for(int i=1;i<=26;i++){
                if(n % i > 0) continue;
                cur = 0;
                for(int j=0;j<i;j++) cur += Math.min(n / i, f.get(j).B.size());
                if(best < cur){
                    best = cur;
                    cnt = i;
                }
            }
            System.out.println(n - best);
            List<Integer> extra = new ArrayList<>();
            char[] res = new char[n];
            for(int i=0;i<cnt;i++){
                for(int j=0;j<n/cnt;j++){
                    if(j < f.get(i).B.size()){
                        res[f.get(i).B.get(j)] = (char) ('a' + f.get(i).A);
                    }else{
                        extra.add(f.get(i).A);
                    }
                }
            }
            int p = 0;
            for(int i=0;i<n;i++){
                System.out.printf("%c", res[i] == 0 ? (char) ('a' + extra.get(p ++)) : res[i]);
            }
            System.out.println();
        }
    }

    //实现cpp里的pair 此处略去
    //public static class Pair<A, B>{}

}

没想到暴力直接取段数

D. Many Perfect Squares

题意

给定数组 \(a\),输出将整个数组加上任意非负数 \(x\) 后完全平方数的最多个数。

思路

首先,答案绝对是 \(\geq1\) 的,那么我们来考虑 \(> 1\) 的情况,也就是在该情况下枚举所有 \(a_i,a_j\),满足 \(a_i+x=p^2,a_j+x=q^2\),然后再枚举所有数,统计完全平方数个数即可。

那么我们来考虑下如何枚举 \(x\)

显然,\(a_j - a_i = q^2 - p^2 = (q - p)(q + p)\),那么 \(q-p\) 即为 \(a_j-a_i\) 的因数,因为数据范围不大,直接暴力枚举所有因数是可行的。

设因数为 \(d\),我们可以得到下面的式子

\(\begin{cases} q - p = d \\ q + p = \frac{a_j - a_i}{d} \end{cases}\)

继续化简,得到

\(\begin{cases} p = \frac{1}{2}(\frac{a_j - a_i}{d} - d) \\ q = \frac{1}{2}(\frac{a_j - a_i}{d} + d) \\ \end{cases}\)

也就是说,只要括号内的式子是偶数,那么 \(d\) 即为一种可行解。

时间复杂度:\(O(n^2f(x)),f(x)为差值的因数个数\)

对应AC代码

#include <bits/stdc++.h>

using namespace std;

const int N = 500010;

#define int long long

int a[60];

signed main() {
    ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
    int t, n;
    cin >> t;
    while(t --){
        cin >> n;
        for(int i=0;i<n;i++) cin >> a[i];
        int ans = 1;
        for(int i=0;i<n-1;i++)
            for(int j=i+1;j<n;j++) {
                int d = a[j] - a[i], x;
                for (int p=1;p<=d/p;p++) {
                    if(d % p == 0){
                        if((d / p - p) % 2 == 1 || (d / p + p) % 2 == 1) continue;
                        x = ((d / p - p) / 2) * ((d / p - p) / 2) - a[i];
                        if(x < 0) continue;
                        int cnt = 0;
                        for(int e=0;e<n;e++){
                            int s = floor(sqrt(a[e] + x));
                            if(s * s == a[e] + x) cnt ++;
                        }
                        ans = max(ans, cnt);
                    }
                }
            }
        cout << ans << '\n';
    }
}

好一个暴力枚举