2023/04/27~28 刷题记录

文章介绍了几个涉及字符串处理和数值计算的算法问题,包括求由1构成的最大矩形面积、寻找数组中MEX值的最优操作、计算数字的幸运度、以及数组元素两两相减的和。每个问题都提供了详细的解题思路和代码实现,强调了观察问题特性、利用贪心策略和排序优化的重要性。

D - JoJo's Incredible Adventures

大致题义:

        有一串由 0,1 构成的字符串,每次循环右移一位,行编号从 0 一直到 n-1。求这些行里由 1 构成的最大矩形面积。

题解: 

         我们其实可以观察到一串连续的 '1' 经过右移后是会形成一对正三角和倒三角的,而矩形就在三角内,答案也在三角内

        也就是说我们可以先找到最长的连续的 '1' 的长度,根据这些长度我们可以做一个矩形面积的求解。可能的解为 1 * len、2 * (len-1)、3 * (len-2)......

        一个 for 循环即可求解,同时也可以进行一个小优化,到了 len/2 的时候停止循环(因为左右相乘的结果对称相等)

         但是这样做完还是不对。因为没有考虑到特殊的情况,在求解 '1' 的最长长度时,可能会出现类似于 '101' 的组合出现(长度看似是 1)。在这样的排序下,当数组右移后,会出现 '110' 的结果,此时最大长度为 2.

         对于这样的问题,我们将 s 成环 转化为 s+s 这样就解决了这个问题。

        但也引入了新的问题,就是出现 '11' 全是 '1' 的这种情况的时候,成环反而出错,所以成环前特判一下。

import java.util.*;

public class Main {
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);

        int t = sc.nextInt();
        sc.nextLine();
        while (t-- != 0) {
            String s = sc.nextLine();
            // 特判全是 '1' 的情况
            if (s.indexOf('0') == -1) {
                System.out.println((long) s.length() * s.length());
                continue;
            }

            // 成环
            s += s;

            char[] chars = s.toCharArray();

            // 找到最长的连续的 '1' 长度
            long k = 0, max = 0;
            for (char aChar : chars) {
                if (aChar == '1')
                    max = Math.max(max, ++k);
                else
                    k = 0;
            }

            // 暴力循环计算
            for (int i = 1; i <= (max + 1) >> 1; i++) {
                k = Math.max(i * (max - i + 1), k);
            }

            System.out.println(k);
        }
    }
}

E - Constructive Problem

题目大意:

        

 题解:

由于MEX值只能增加 1 ,所以我们的操作需要:

  • 删除数组中所有的 MEX+1,又因为我们仅仅能操作一次,让某个区间内的所有元素改为 x
  • 添加 MEX,故我们可以 把 x = MEX

所以只要找到MEX+1在数组中的第一次出现和最后一次出现的下标设为 l,r ,则必须将 [l,r] (左闭右闭)范围内元素均改为 MEX。这是最小的范围,如果范围再增大,可能会覆盖掉一些别的元素,可能使得 MEX 值反而减小。因此从贪心角度,选取 [l,r] 改为 MEX 是最优的。

操作之后,计算操作后的 MEX 值,看是否与之前的值相比 增大了 1,输出结果

特别地,如果数组中没有出现MEX+1:

  • 如果数组中每个元素都对MEX值有贡献(数组是 0,1,2,⋯,MEX-1 的一个排列),则答案是NO。
  • 对于其他情况,选择一个冗余元素改为MEX即可。
import java.util.*;

public class Main {
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);

        int t = sc.nextInt();
        sc.nextLine();
        while (t-- != 0) {
            int n = sc.nextInt();

            // 输入
            ArrayList<Integer> list = new ArrayList<>(n);
            for (int i = 0; i < n; i++) {
                list.add(sc.nextInt());
            }

            // 将元素添加进 HashSet 集合
            HashSet<Integer> all = new HashSet<>(list);

            // 计算 MEX
            int MEX1 = 0;
            while (all.contains(MEX1))
                MEX1++;

            // 特殊情况 01234 等,修改不了
            if (MEX1 == list.size()) {
                System.out.println("No");
                continue;
            }

            // 重新创建一个 HashSet,将 原来的元素去掉中间那一段的 MEX 到 MEX 的元素
            all = new HashSet<>(n);
            all.add(MEX1);
            // b 就是用来剪枝的
            boolean b = false;
            for (Integer e : list) {
                if (e == MEX1 + 1) {
                    b = true;
                    break;
                } else {
                    all.add(e);
                }
            }
            if (b) {
                for (int i = n - 1; i >= 0; i--) {
                    Integer e = list.get(i);

                    if (e == MEX1 + 1) {
                        break;
                    } else {
                        all.add(e);
                    }
                }
            }

            // 去掉中间那段元素后 重新求 MEX
            int MEX2 = 0;
            while (all.contains(MEX2))
                MEX2++;

            if (MEX1 + 1 == MEX2)
                System.out.println("Yes");
            else
                System.out.println("No");
        }
    }
}

I - Lucky Numbers

题义:

        奥林巴斯城最近推出了个人星舰的生产。现在火星上的每个人都可以买一个,然后以低廉的价格飞往其他星球。每艘星舰都有一个数字正整数z。让我们将数字z的幸运度定义为该数字的最大数字和最小数字之差。例如,142857的最大数字是8,最小数字是1,所以它的幸运度是8 - 1 = 7。数字111的所有数字都等于1,所以它的幸运度是0。Hateehc是一位著名的火星博主,他经常飞往太阳系的不同角落。为了更快地发布有趣的视频,他决定给自己买一艘星际飞船。当他来到商店时,他看到了数字从l到r的星际飞船。在商店里,海蒂想找一艘有最幸运数字的星际飞船。因为商店里有很多星际飞船,而Hateehc不会编程,所以你必须帮助博主编写一个程序来回答他的问题。

题解:

        幸运数字一共有四种情况可以出现,分别是 以0、9结尾,还有输入的 a 或 b 本身。

        对四种情况分别讨论。求得最大幸运(9)就剪枝。

        关于为什么要 +- 10。因为 100 肯定是没有 90 幸运的。

import java.util.*;

public class Main {
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);

        int t = sc.nextInt();
        sc.nextLine();
        while (t-- != 0) {
            int ans = 0;
            int a = sc.nextInt();
            int b = sc.nextInt();

            int temp;

            // 本题一共有四个分类,分别是以 0、9 结尾和输入的两个边界值
            // 以 0 结尾的答案
            temp = b / 10 * 10;
            while (temp >= a) {
                ans = fun(ans, temp);

                temp -= 10;

                if (fun(ans) == 9) {
                    break;
                }
            }

            // 以 9 结尾的答案
            temp = a / 10 * 10 + 9;
            while (b >= temp) {
                ans = fun(ans, temp);

                temp += 10;

                if (fun(ans) == 9) {
                    break;
                }
            }

            // 边界答案
            ans = fun(ans, a);
            ans = fun(ans, b);
            System.out.println(ans);
        }
    }

    // 传两个值进去,返回 幸运值大的 元素
    static int fun(int a, int b) {
        return fun(a) > fun(b) ? a : b;
    }

    //求某个值的幸运值
    static int fun(int x) {
        return funMax(x) - funMin(x);
    }

    // 求位数上最大的值
    static int funMax(int x) {
        int t = x % 10;
        while (x > 0) {
            t = Math.max(t, x % 10);

            x /= 10;
        }
        return t;
    }

    // 求位数上最小的值
    static int funMin(int x) {
        int t = x % 10;
        while (x > 0) {
            t = Math.min(t, x % 10);

            x /= 10;
        }
        return t;
    }
}

J - Playing in a Casino

题目大意:

给出一个 T 组样例,每组样例给出一个 n 和 m 表示给出n条数据,每条有 m 个数据

每次选两条,每个数据一一对应相减取绝对值,求绝对值的和是多少。

样例提示
3 5
1 4 2 8 5
7 9 2 1 4
3 8 5 3 1


|1−7|+|4−9|+|2−2|+|8−1|+|5−4|=19

|1−3|+|4−8|+|2−5|+|8−3|+|5−1|=18

|7−3|+|9−8|+|2−5|+|1−3|+|4−1|=13|7−3|+|9−8|+|2−5|+|1−3|+|4−1|=13 

19+18+13=5019+18+13=50 

 题解:

        题义很明确,直接想到用暴力求解,把每列数据的每行两两组合相减 求绝对值相加的和就是结果。但是应该可以想到这个复杂度,肯定会炸的。

        继续观察我们会发现每个数据对应的列的位置不变,改变数据所在的行数位置不会对答案造成影响

        所以我们能通过排序将数据重新规划

        还是拿样例来说,排序后就如下👇

1 4 2 1 1
3 8 2 3 4 
7 9 5 8 5

所以我们可以对每一列进行排序,排序之后相减的结果都是正数就可以不用取绝对值了。既然不用绝对值,我们就可以使用前缀和的思想,计算每列每个元素的贡献。贡献值为 后面所有的元素之和 减去 计算贡献的元素*后面元素的个数(因为要跟后面每个元素相减)。代码如下👇

import java.util.*;

public class Main {
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);

        int t = sc.nextInt();
        sc.nextLine();
        while (t-- != 0) {
            int n = sc.nextInt();
            int m = sc.nextInt();

            // 初始化集合
            ArrayList<ArrayList<Long>> arrayLists = new ArrayList<>(m);
            for (int i = 0; i < m; i++) {
                arrayLists.add(new ArrayList<>());
            }

            // 按照一列一列的输入
            for (int i = 0; i < n; i++) {
                for (int j = 0; j < m; j++) {
                    arrayLists.get(j).add(sc.nextLong());
                }
            }

            // 开 long 存储数据
            long ans = 0;
            // 计算每列的和
            for (int i = 0; i < m; i++) {
                ArrayList<Long> integers = arrayLists.get(i);
                Collections.sort(integers);

                // 使用Java 8的Stream API计算列表中元素之和
                long sum = integers.stream().mapToLong(Long::longValue).sum();

                // 计算一列中,每个元素的贡献,因为排过序了,所以不用取绝对值
                // 故可以简化每次的加法,一次性将一个元素与其他所有元素相减
                for (int j = 0; j < n - 1; j++) {
                    sum -= integers.get(j);

                    ans += sum - integers.get(j) * (n - j - 1);
                }
            }

            System.out.println(ans);
        }
    }
}

 K - Showstopper

题目大意:
给 a,b 两个数组,你可以进行无限次的操作,使得这两个数组最后一个元素最大。

  • (1 <= i <= n)取一个 i,将数组中 a[i] 与 b[i] 调换

题解:
我们对每一列的元素进行排序,进行简单判断就好。水题

import java.util.*;

public class Main {
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);

        int t = sc.nextInt();
        sc.nextLine();
        while (t-- != 0) {
            int n = sc.nextInt();

            ArrayList<ArrayList<Integer>> arrayLists = new ArrayList<>(n);
            for (int i = 0; i < n; i++) {
                arrayLists.add(new ArrayList<>(2));
            }

            for (int i = 0; i < n; i++) {
                arrayLists.get(i).add(sc.nextInt());
            }
            for (int i = 0; i < n; i++) {
                arrayLists.get(i).add(sc.nextInt());
            }

            ArrayList<Integer> r = arrayLists.get(n - 1);
            Collections.sort(r);

            boolean b = true;
            for (int i = 0; i < n - 1; i++) {
                ArrayList<Integer> integers = arrayLists.get(i);
                Collections.sort(integers);

                if (integers.get(0) > r.get(0) || integers.get(1) > r.get(1)) {
                    b = false;
                    break;
                }
            }

            if (b)
                System.out.println("YES");
            else
                System.out.println("NO");
        }
    }
}

L - Three Sevens

题目大意:

        给定m天,每天n个人可以中奖,当前中奖的人不能参加后面的比赛,输出可能的方案

题目让我输出一个 没有冲突的方案,如果一个人在多天都能得奖,那么我们任选其中一天让他得奖,这种人可能存在多个,我们把同一天的这类人看成一组,这一天我们选择了这组中的其中一个人相当于选择了这个组,在之后就不能再选择属于这组中的人了

然后题目等价与在每天的那个组中选一个人,总的集合 向 当前天的选择的那个人(总的集合没有的人) 连边,然后这一天的这一组也属于 总的集合

组1 ---组2  -- 组3 ----...

== 组12 -- 组3 ---...

最后 每天 的 单独的一个集合 合并为一个大的集合 ,就说明有解,如果其中一个集合无法合并就说明无解

要保证前面选择的 不会和 后面的有冲突,我们从后面开始选,选完后把这组人全部标记 

题解:

        根据题义,只要在后面的日子里面参加了抽奖的 都不能作为前面中奖的,所以只要我们倒着将所有的天遍历,把一天里面所有的人全部放进一个集合里面,这个集合就是后面参与抽奖的人只要在这个集合里面,就不能中奖了。只要一天之中有一个人没有在这个集合里面,就可以作为中奖的人。遍历每一天,每一天都可以有人中奖,则为YES,否则为NO。

import java.util.*;

public class Main {
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);

        int t = sc.nextInt();
        sc.nextLine();
        while (t-- != 0) {
            int m = sc.nextInt();

            // 初始化 输入
            ArrayList<ArrayList<Integer>> arrayLists = new ArrayList<>(m);
            for (int i = 0; i < m; i++) {
                int n = sc.nextInt();

                ArrayList<Integer> integers = new ArrayList<>(n);
                for (int j = 0; j < n; j++) {
                    integers.add(sc.nextInt());
                }

                arrayLists.add(integers);
            }

            // 使用 HashSet 去做全局的一个集合,在使用 contains 方法时可以快速的返回结果
            // 如果使用 ArrayList、LinkedList 等数据结构,contains 方法需要遍历集合,耗时很长
            HashSet<Integer> all = new HashSet<>();
            ArrayList<Integer> ans = new ArrayList<>(arrayLists.size());
            int k;
            // 倒着遍历只要在后面出现过的人 都不能作为前面中奖的人
            for (int i = arrayLists.size() - 1; i >= 0; i--) {
                ArrayList<Integer> integers = arrayLists.get(i);
                k = 0;

                for (Integer integer : integers) {
                    // 只要有一个在后面没有出现过,就作为中奖的
                    if (!all.contains(integer)) {
                        k = integer;
                        break;
                    }
                }
                // 将后面出现过的人 全部添加到 HashSet 集合中
                all.addAll(integers);

                if (k == 0)
                    break;
                else
                    ans.add(k);
            }
            // 输出结果
            if (ans.size() == arrayLists.size()) {
                for (int i = ans.size() - 1; i >=0; i--) {
                    System.out.print(ans.get(i) + " ");
                }
                System.out.println();
            } else
                System.out.println(-1);
        }
    }
}

N - Sum on Subarrays

题目大意:

构造题。 

构造方案为,在前面全部填 2 后面填一个数,再后面填负无穷。

考虑在一串数后面加一个数的贡献,是所有后缀和(包括空后缀)加这个数大于 0 的个数。

每一个数字的贡献最多是这个数字的序号(因为这个数本身为正,然后分别和前面 n-1 的数的前缀和相加 都为正的话,最大贡献为 n)。

所以我们可以得到,一个 2 的贡献为 序号 i,那么所有的贡献值为 1,2,3,,,n。相加有公式 (1+n)*n/2。添加每一个 '2' 时,判断是否还需要 n 的贡献,如果不需要这么多,那么就根据还需要多少贡献去计算一个数字。如下,需要 1 的贡献,需要补充一个 -1 去和 第一个 2 相加,就可以补充一个贡献。

比如3 2  ——>  2 -1 -1000

由此可以推断,需要几个贡献就用👇

前缀和的取反 + 2*需要的贡献 + 1

import java.util.*;

public class Main {
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);

        int t = sc.nextInt();
        sc.nextLine();
        while (t-- != 0) {
            int n = sc.nextInt();
            int k = sc.nextInt();

            for (int i = 1; i <= n; i++) {
                if (k >= i * (i + 1) / 2)
                    System.out.print(2 + " ");
                    // 如果需要的贡献大于 0
                else if (k - i * (i - 1) / 2 > 0)
                    System.out.print(-2 * i + (k - i * (i - 1) / 2) * 2 + 1 + " ");
                else
                    System.out.print(-1000 + " ");
            }
            System.out.println();
        }
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值